[Archived] Archived WIP of vielle.dev
1---
2import { blog, utils } from "@/config";
3
4interface Props {
5 id: number;
6 of: number;
7}
8
9const { id, of } = Astro.props;
10
11const length = utils.getRandom(blog.balloons.length);
12const offset = utils.getRandom(blog.balloons.offset);
13const rotation = new Array(5)
14 .fill(0)
15 .map((_) => utils.getRandom(blog.balloons.rotation));
16---
17
18<div
19 class="cable"
20 style={`--length: ${length}px;
21 --id: ${id};
22 --of: ${of};
23 --offset: ${offset}px;
24 ${rotation.map((x, i) => `--rot-${i}: ${x}deg;`).join(" ")}
25 --timing: ${utils.getRandom(blog.balloons.timing)}s;
26 `}
27>
28 <div
29 class="balloon"
30 style={`--width: ${utils.getRandom(blog.balloons.size[0])}px;
31 --height: ${utils.getRandom(blog.balloons.size[1])}px;`}
32 tabindex="-1"
33 data-min-time={blog.balloons.time[0]}
34 data-max-time={blog.balloons.time[1]}
35 role="none"
36 >
37 <div class="cable-tie"></div>
38 <div class="tie"></div>
39 </div>
40</div>
41
42<script>
43 const balloons = document.querySelectorAll(".balloon");
44 balloons.forEach((el) => {
45 if (!(el instanceof HTMLElement)) return;
46
47 const mintime = Number(el.dataset["minTime"] ?? "0");
48 const maxtime = Number(el.dataset["maxTime"] ?? "0");
49
50 el.addEventListener("click", () => {
51 const cableParent = el.parentElement;
52 const postParent = el.parentElement?.parentElement;
53 if (!cableParent) throw new Error("No parent 1 level up!!!");
54 if (!postParent) throw new Error("No parent 2 levels up!!!");
55
56 el.blur();
57
58 Promise.all([
59 el.animate(
60 { opacity: [1, 0] },
61 {
62 duration: 100,
63 fill: "forwards",
64 },
65 ).finished,
66
67 cableParent.animate(
68 [
69 {},
70 {
71 height: 0,
72 top: 0,
73 },
74 ],
75 {
76 duration: 500,
77 fill: "forwards",
78 },
79 ),
80
81 postParent.animate(
82 [
83 {},
84 {
85 top: "calc(var(--x-offset-0) + 500px)",
86 },
87 ],
88 {
89 duration: 1000,
90 easing: "ease-in-out",
91 },
92 ).finished,
93 ]).then(() => {
94 const duration = (mintime + Math.random() * (maxtime - mintime)) * 1000;
95
96 el.animate(
97 {
98 opacity: [0, 1],
99 scale: [0, 1],
100 offset: [0, 1],
101 },
102 {
103 duration,
104 fill: "forwards",
105 // easing: "ease-in",
106 },
107 );
108
109 cableParent.animate(
110 [
111 {
112 height: 0,
113 top: 0,
114 },
115 {
116 height: "var(--length)",
117 top: "calc(-1 * var(--length))",
118 },
119 ],
120 {
121 duration,
122 fill: "forwards",
123 },
124 );
125
126 postParent.animate(
127 [
128 {
129 top: "calc(var(--x-offset-0) + 500px)",
130 },
131 {},
132 ],
133 {
134 duration,
135 fill: "forwards",
136 easing: "ease-in",
137 },
138 );
139 });
140 });
141 });
142</script>
143
144<style>
145 @property --rot-0 {
146 syntax: "<angle>";
147 inherits: false;
148 initial-value: 0px;
149 }
150
151 @property --rot-1 {
152 syntax: "<angle>";
153 inherits: false;
154 initial-value: 0px;
155 }
156
157 @property --rot-2 {
158 syntax: "<angle>";
159 inherits: false;
160 initial-value: 0px;
161 }
162
163 @property --rot-3 {
164 syntax: "<angle>";
165 inherits: false;
166 initial-value: 0px;
167 }
168
169 @property --rot-4 {
170 syntax: "<angle>";
171 inherits: false;
172 initial-value: 0px;
173 }
174
175 @keyframes tilt {
176 from,
177 to {
178 rotate: var(--rot-0);
179 }
180
181 20% {
182 rotate: var(--rot-1);
183 }
184
185 40% {
186 rotate: var(--rot-2);
187 }
188
189 60% {
190 rotate: var(--rot-3);
191 }
192
193 80% {
194 rotate: var(--rot-4);
195 }
196 }
197
198 @keyframes inv-tilt {
199 from,
200 to {
201 rotate: calc(-1 * var(--rot-0));
202 }
203
204 20% {
205 rotate: calc(-1 * var(--rot-1));
206 }
207
208 40% {
209 rotate: calc(-1 * var(--rot-2));
210 }
211
212 60% {
213 rotate: calc(-1 * var(--rot-3));
214 }
215
216 80% {
217 rotate: calc(-1 * var(--rot-4));
218 }
219 }
220
221 @keyframes bouncing {
222 from,
223 to {
224 scale: 1 1;
225 }
226 50% {
227 scale: 1.05 1.1;
228 }
229 }
230
231 .cable {
232 position: absolute;
233
234 width: 5px;
235 height: var(--length);
236 border-radius: 2.5px;
237 background: black;
238
239 [data-time="night"] + * & {
240 background: #404040;
241 }
242
243 z-index: -99;
244 top: calc(-1 * var(--length));
245 left: calc(
246 100% / var(--of) * var(--id) + 100% / 2 / var(--of) + var(--offset)
247 );
248
249 transform-origin: bottom;
250 rotate: var(--rot-0);
251
252 @media (prefers-reduced-motion: no-preference) {
253 animation: infinite var(--timing) linear tilt;
254 }
255 }
256
257 .balloon {
258 border: none;
259
260 width: var(--width);
261 height: var(--height);
262 border-radius: calc(var(--width) / 2);
263
264 /* inherits from parent <Post /> */
265 background: var(--colour);
266 /* 95% instead of 100% to prevent gaps between cable */
267 translate: -50% -95%;
268 transform-origin: bottom;
269 rotate: calc(-1 * var(--rot-0));
270
271 @media (prefers-reduced-motion: no-preference) {
272 animation: infinite var(--timing) linear inv-tilt;
273 }
274 }
275
276 .cable-tie {
277 width: 17.5px;
278 height: 5px;
279
280 position: absolute;
281 bottom: -2.5px;
282 left: 50%;
283 translate: -50%;
284 z-index: 1;
285
286 border-radius: 2.5px;
287 background-color: black;
288
289 [data-time="night"] + * & {
290 background: #404040;
291 }
292 }
293
294 .tie {
295 width: 20px;
296 height: 20px;
297 background-color: var(--colour);
298 clip-path: polygon(50% 0, 0 100%, 100% 100%);
299 position: absolute;
300 bottom: -10px;
301 left: 50%;
302 translate: -50%;
303 }
304</style>