apptify 0.8.1 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apptify.css +1 -1
- package/dist/apptify.js +1503 -1721
- package/dist/components/button.d.ts +1 -1
- package/dist/components/foundation.d.ts +2 -4
- package/dist/components/link.d.ts +1 -1
- package/dist/core/image-gallery-motion.d.ts +1 -1
- package/dist/core/layers.d.ts +0 -1
- package/dist/index.d.ts +2 -46
- package/docs/COMPONENTS.md +7 -4
- package/package.json +1 -1
- package/templates/playground/README.md +1 -1
- package/templates/playground/package.json +1 -1
- package/templates/playground/source-snapshot.json +73 -42
- package/templates/playground/src/ComponentDemo.vue +2 -5
- package/templates/playground/src/catalog.ts +0 -1
- package/templates/playground/src/components/ErrorPagePreviews.vue +86 -0
- package/templates/playground/src/components/bad-request-paper-scene.vue +280 -0
- package/templates/playground/src/components/bad-request-paper-story.ts +169 -0
- package/templates/playground/src/components/hamster-429-scene.vue +1045 -0
- package/templates/playground/src/components/server-fire-paper-scene.vue +249 -0
- package/templates/playground/src/components/server-fire-paper-story.ts +272 -0
- package/templates/playground/src/components/unavailable-paper-scene.vue +488 -0
- package/templates/playground/src/editor/component-metadata.ts +0 -1
- package/templates/playground/src/editor/handbooks/apple-dialog.ts +1 -1
- package/templates/playground/src/editor/playground-examples.ts +0 -1
- package/templates/playground/src/error-pages.ts +9 -0
- package/templates/playground/src/style.css +0 -2
- package/templates/playground/src/views/400.vue +74 -0
- package/templates/playground/src/views/403.vue +5 -2
- package/templates/playground/src/views/429.vue +74 -0
- package/templates/playground/src/views/500.vue +67 -0
- package/templates/playground/src/views/502.vue +5 -2
- package/templates/playground/src/views/503.vue +79 -0
- package/templates/playground/src/views/[...all].vue +5 -2
- package/templates/playground/src/views/components.vue +8 -8
- package/tooling/prepare-playground.mjs +2 -2
- package/dist/components/scrollbar.d.ts +0 -88
- package/dist/core/scrollbar.d.ts +0 -14
- package/templates/playground/src/editor/handbooks/apple-scroll-bar.ts +0 -15
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<figure ref="scene" class="fire-scene" :class="{ 'fire-scene--dark': dark }" :data-story="phase" :data-playing="canAnimate && !suspended">
|
|
3
|
+
<div class="fire-scene__window">
|
|
4
|
+
<svg ref="art" class="fire-scene__art" viewBox="0 0 1000 660" role="img" :aria-labelledby="`${uid}-title ${uid}-desc`" xmlns="http://www.w3.org/2000/svg">
|
|
5
|
+
<title :id="`${uid}-title`">猴子的最后一单</title>
|
|
6
|
+
<desc :id="`${uid}-desc`">猴子抱着包裹送进服务器。镜头拉远,服务器突然爆炸。猴子双手抱头,望着持续燃烧的服务器。</desc>
|
|
7
|
+
<defs>
|
|
8
|
+
<clipPath :id="`${uid}-frame`"><path d="M36 74Q37 38 75 35L909 20Q957 19 963 68L986 565Q987 608 942 614L84 635Q29 635 27 585Z" /></clipPath>
|
|
9
|
+
<clipPath :id="`${uid}-window`"><path d="M108 332V202C108 83 306 83 306 202V332Z" /></clipPath>
|
|
10
|
+
<clipPath :id="`${uid}-packet-clip`"><path d="M-1200-800H662V1400H-1200Z" /></clipPath>
|
|
11
|
+
<filter :id="`${uid}-shadow`" x="-30%" y="-25%" width="170%" height="175%"><feDropShadow dx="1" dy="5" stdDeviation="2.5" flood-color="#3d3429" flood-opacity=".14" /></filter>
|
|
12
|
+
<pattern :id="`${uid}-grain`" width="41" height="37" patternUnits="userSpaceOnUse"><path d="M4 6h1m15 11h1m13-9h.7M9 29h1m26 3h.8" stroke="#5e472e" stroke-opacity=".11" stroke-width="1" /><path d="M8 11l3-1m13 16 3-1m-4-23 2-1" stroke="#fff8e5" stroke-opacity=".24" stroke-width=".7" /></pattern>
|
|
13
|
+
<g :id="`${uid}-leaf`"><path d="M0 0C-37-5-50-27-37-54-10-49 6-25 0 0Z" fill="currentColor" /><path d="M-1-3-31-46" fill="none" stroke="#eee2b8" stroke-width="1.4" opacity=".5" /></g>
|
|
14
|
+
<g :id="`${uid}-smoke-shape`"><path d="M-36 12C-60 8-57-20-38-24-44-47-13-58-1-40 12-65 45-51 43-29 67-25 65 3 48 11 54 32 25 40 12 28-6 47-34 36-36 12Z" fill="var(--fire-smoke)" /><path d="M-38-24C-44-47-13-58-1-40 12-65 45-51 43-29 16-38 16-15-2-8-19-1-26-18-38-24Z" fill="var(--fire-smoke-light)" opacity=".55" /></g>
|
|
15
|
+
</defs>
|
|
16
|
+
<path d="M44 90Q43 52 83 49L915 33Q970 35 974 85L994 574Q996 620 945 628L86 647Q35 647 36 598Z" fill="var(--fire-frame)" />
|
|
17
|
+
<g :clip-path="`url(#${uid}-frame)`">
|
|
18
|
+
<path d="M0 0H1000V660H0Z" fill="var(--fire-wall)" />
|
|
19
|
+
<g data-part="camera">
|
|
20
|
+
<path d="M-1000-800H2000V1100H-1000Z" fill="var(--fire-wall)" />
|
|
21
|
+
<path d="M-1000 365H827L1180 243V980H-1000Z" fill="var(--fire-panel)" />
|
|
22
|
+
<path d="M827-800H2000V1100H827Z" fill="var(--fire-sidewall)" opacity=".52" />
|
|
23
|
+
<path d="M-1000 444H827L2000 206V1200H-1000Z" fill="var(--fire-floor)" />
|
|
24
|
+
<path d="M-1000 443H827L2000 205" fill="none" stroke="var(--fire-baseboard)" stroke-width="12" />
|
|
25
|
+
<path d="M-300 541 827 444 1275 603M48 690 827 444M510 750 827 444" fill="none" stroke="var(--fire-floor-line)" stroke-width="1.4" opacity=".38" />
|
|
26
|
+
<path d="M85 338V202C85 54 329 54 329 202V346Z" fill="var(--fire-window-dark)" :filter="`url(#${uid}-shadow)`" />
|
|
27
|
+
<path d="M96 334V202C96 70 318 70 318 202V338Z" fill="var(--fire-window-frame)" />
|
|
28
|
+
<g :clip-path="`url(#${uid}-window)`">
|
|
29
|
+
<path d="M92 84H320V348H92Z" fill="var(--fire-window-sky)" />
|
|
30
|
+
<circle cx="247" cy="168" r="30" fill="var(--fire-sun)" />
|
|
31
|
+
<path d="M78 265 130 208 193 248 242 220 333 281V352H78Z" fill="var(--fire-hill-far)" />
|
|
32
|
+
<path d="m130 208 63 40-38 15-25-12-52 14Z" fill="var(--fire-hill-fold)" />
|
|
33
|
+
<path d="M82 304 167 261 231 288 279 263 333 304V352H82Z" fill="var(--fire-hill-near)" />
|
|
34
|
+
<path d="m116 262-21 38h12l-25 31h62l-22-31h12Zm156 13-25 42h15l-21 29h61l-20-29h13Z" fill="var(--fire-leaf-dark)" />
|
|
35
|
+
</g>
|
|
36
|
+
<path d="M202 102V333M108 244H306" fill="none" stroke="var(--fire-window-frame)" stroke-width="10" />
|
|
37
|
+
<path d="M105 334H315L327 349H94Z" fill="var(--fire-window-frame)" /><path d="M94 349H327V358H94Z" fill="var(--fire-window-dark)" />
|
|
38
|
+
<path d="M478-50V119" stroke="var(--fire-cable)" stroke-width="3" />
|
|
39
|
+
<path d="m454 115 48 0 30 34-107 0Z" fill="var(--fire-metal)" /><path d="m454 115 24 0-2 34-51 0Z" fill="var(--fire-metal-light)" /><ellipse cx="478" cy="150" rx="53" ry="7" fill="var(--fire-lamp)" />
|
|
40
|
+
<path d="M718-80V132Q718 149 735 149H816Q833 149 833 168V192" fill="none" stroke="var(--fire-conduit)" stroke-width="7" />
|
|
41
|
+
<path d="M718-80V132Q718 149 735 149H816Q833 149 833 168" fill="none" stroke="var(--fire-conduit-light)" stroke-width="2" />
|
|
42
|
+
<path d="M811 145v9m-92-67h-7m113 55-6 11" stroke="var(--fire-conduit-dark)" stroke-width="4" />
|
|
43
|
+
<g transform="translate(129 525)" :filter="`url(#${uid}-shadow)`">
|
|
44
|
+
<path d="M-35-57H34L25 0-18 4Z" fill="var(--fire-pot)" /><path d="m-35-57 20 0 9 60-12 1Z" fill="var(--fire-pot-light)" /><ellipse cy="-57" rx="35" ry="8" fill="var(--fire-pot-dark)" />
|
|
45
|
+
<path d="M0-55-10-164m9 109 21-123m-21 121-41-90m41 85 44-66" fill="none" stroke="var(--fire-leaf-dark)" stroke-width="4" />
|
|
46
|
+
<g color="var(--fire-leaf-dark)"><use :href="`#${uid}-leaf`" transform="translate(-9 -129) rotate(-25) scale(.65)" /><use :href="`#${uid}-leaf`" transform="translate(15 -142) rotate(59) scale(.8)" /><use :href="`#${uid}-leaf`" transform="translate(-25 -104) rotate(-46) scale(.78)" /></g>
|
|
47
|
+
<g color="var(--fire-leaf-light)"><use :href="`#${uid}-leaf`" transform="translate(-6 -116) rotate(75) scale(.72)" /><use :href="`#${uid}-leaf`" transform="translate(27 -99) rotate(88) scale(.88)" /><use :href="`#${uid}-leaf`" transform="translate(-7 -80) rotate(-65) scale(.76)" /></g>
|
|
48
|
+
</g>
|
|
49
|
+
<ellipse cx="765" cy="544" rx="136" ry="19" fill="var(--fire-ground-shadow)" opacity=".16" />
|
|
50
|
+
<ellipse data-part="monkey-shadow" cx="190" cy="540" rx="80" ry="12" fill="var(--fire-ground-shadow)" opacity=".17" />
|
|
51
|
+
<ellipse data-part="fire-glow" cx="757" cy="529" rx="143" ry="31" fill="#e5aa52" opacity="0" />
|
|
52
|
+
<g data-part="smoke" opacity="0">
|
|
53
|
+
<g data-smoke="0"><use :href="`#${uid}-smoke-shape`" /></g><g data-smoke="1"><use :href="`#${uid}-smoke-shape`" /></g><g data-smoke="2"><use :href="`#${uid}-smoke-shape`" /></g><g data-smoke="3"><use :href="`#${uid}-smoke-shape`" /></g><g data-smoke="4"><use :href="`#${uid}-smoke-shape`" /></g><g data-smoke="5"><use :href="`#${uid}-smoke-shape`" /></g>
|
|
54
|
+
</g>
|
|
55
|
+
<g data-part="server" :filter="`url(#${uid}-shadow)`">
|
|
56
|
+
<path d="m652 506 45 25v18l-45-25Zm47 27h164v16H699Z" fill="var(--fire-metal-dark)" />
|
|
57
|
+
<path d="M650 180 698 207V536L650 509Z" fill="var(--fire-metal-dark)" />
|
|
58
|
+
<path d="M650 180 664 189V512L650 509Z" fill="var(--fire-metal)" />
|
|
59
|
+
<path d="M698 207H862V536H698Z" fill="var(--fire-metal)" />
|
|
60
|
+
<path d="M698 207H710V536H698Z" fill="var(--fire-metal-light)" /><path d="M848 207H862V536H848Z" fill="var(--fire-metal-dark)" />
|
|
61
|
+
<g data-part="server-lid"><path d="M650 180 814 180 862 207H698Z" fill="var(--fire-metal-light)" /><path d="m650 180 164 0 11 6-164 0Z" fill="var(--fire-metal-pale)" /><path d="m706 193 72 0 7 4-73 0Z" fill="var(--fire-metal)" /></g>
|
|
62
|
+
<path d="M718 257H843V508H718Z" fill="var(--fire-cavity)" />
|
|
63
|
+
<g data-part="rack-upper"><path d="M717 261H844V305H717Z" fill="var(--fire-panel-front)" /><path d="M726 270h65m-65 8h65m-65 8h65" stroke="var(--fire-vent)" stroke-width="3" stroke-linecap="round" /><circle data-led="0" cx="826" cy="279" r="4.3" fill="#b8ce8b" /><circle cx="812" cy="279" r="2.1" fill="var(--fire-metal-pale)" /></g>
|
|
64
|
+
<g data-part="rack-lower"><path d="M717 452H844V497H717Z" fill="var(--fire-panel-front)" /><path d="M726 463h64m-64 8h64m-64 8h64" stroke="var(--fire-vent)" stroke-width="3" stroke-linecap="round" /><circle data-led="1" cx="826" cy="472" r="4.3" fill="#b8ce8b" /><circle cx="812" cy="472" r="2.1" fill="var(--fire-metal-pale)" /></g>
|
|
65
|
+
<g data-part="server-damage" opacity="0">
|
|
66
|
+
<path d="m710 324 21-12 10 12 19-16 18 11 26-12 20 14 18-8 8 39-12 25 13 28-17 36-30-4-22 11-20-10-24 9-23-23Z" fill="#302f27" />
|
|
67
|
+
<path d="m735 324 11 25-10 26 24 17-4 40m45-115-13 32 19 20-11 24 7 37" fill="none" stroke="#98836a" stroke-width="4" />
|
|
68
|
+
<path data-part="wire-one" d="M754 348Q714 344 716 390T684 414" fill="none" stroke="#b99b60" stroke-width="5" stroke-linecap="round" />
|
|
69
|
+
<path data-part="wire-two" d="M806 367Q851 361 855 391T881 423" fill="none" stroke="#b67851" stroke-width="4" stroke-linecap="round" />
|
|
70
|
+
<path d="m724 247 7-9 6 12 9-6m88 249-9 16 6 8-10 17" fill="none" stroke="var(--fire-cavity)" stroke-width="2.5" />
|
|
71
|
+
</g>
|
|
72
|
+
<g data-part="server-door"><path d="M716 318H845V441H716Z" fill="var(--fire-panel-front)" /><path d="M718 320H843V327H718Z" fill="var(--fire-metal-light)" /><path d="M727 336h68m-68 8h68m-68 8h68m-68 21h68m-68 8h68m-68 8h68m-68 21h68m-68 8h68m-68 8h68" stroke="var(--fire-vent)" stroke-width="3" stroke-linecap="round" /><circle data-led="2" cx="826" cy="346" r="4.3" fill="#b8ce8b" /><circle data-led="3" cx="826" cy="383" r="4.3" fill="#b8ce8b" /><circle data-led="4" cx="826" cy="420" r="4.3" fill="#b8ce8b" /><path d="m835 362 0 34" stroke="var(--fire-metal-pale)" stroke-width="3" stroke-linecap="round" /></g>
|
|
73
|
+
<path d="m645 370 49 27v77l-49-27Z" fill="var(--fire-metal-pale)" /><path d="m650 380 44 24v60l-44-24Z" fill="var(--fire-cavity)" />
|
|
74
|
+
<path d="m652 440 42 24v10l-49-27Z" fill="var(--fire-metal-dark)" />
|
|
75
|
+
<path d="m658 272 27 16m-27-7 27 16m-27-7 27 16" stroke="var(--fire-vent)" stroke-width="3" />
|
|
76
|
+
<text x="779" y="241" text-anchor="middle" fill="var(--fire-label)" font-family="ui-monospace, SFMono-Regular, Consolas, monospace" font-size="18" letter-spacing="1.6">server</text>
|
|
77
|
+
<g fill="var(--fire-metal-pale)"><circle cx="707" cy="217" r="2" /><circle cx="853" cy="217" r="2" /><circle cx="707" cy="524" r="2" /><circle cx="853" cy="524" r="2" /></g>
|
|
78
|
+
</g>
|
|
79
|
+
<g data-part="monkey-back" transform="translate(190 536)">
|
|
80
|
+
<g data-part="monkey-back-upper">
|
|
81
|
+
<g data-part="arm-front"><path data-part="arm-front-main" d="M33-128Q50-83 108-87" fill="none" stroke="#ad7951" stroke-width="24" stroke-linecap="round" stroke-linejoin="round" /><path data-part="arm-front-fold" d="M34-133 54-95 108-91" fill="none" stroke="#c49468" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" /></g>
|
|
82
|
+
<g data-part="hand-front" transform="translate(108 -87) rotate(-12)"><path d="M-11-6Q-11-15-4-14L9-11Q16-8 13-1L9 7Q2 12-8 6L-13 2Z" fill="#e4be88" /><path d="m-6-7 13 2m-14 3 13 2" stroke="#a57550" stroke-width="1.2" stroke-linecap="round" fill="none" /></g>
|
|
83
|
+
</g>
|
|
84
|
+
</g>
|
|
85
|
+
<g :clip-path="`url(#${uid}-packet-clip)`">
|
|
86
|
+
<g data-part="packet" transform="translate(265 427)">
|
|
87
|
+
<path d="m-44-30 73-4 17 15-73 5Z" fill="#e9c68c" /><path d="m-27-14 73-5v59l-73 4Z" fill="#c59459" /><path d="m-44-30 17 16v58l-17-17Z" fill="#ae7c48" />
|
|
88
|
+
<path d="m-17-32 14-1 17 16-14 1Z" fill="#f4dfb2" /><path d="m0-16 14-1v59l-14 1Z" fill="#e5c18b" />
|
|
89
|
+
<path d="m-44-30 17 16 73-5M-27-14v58" fill="none" stroke="#8d643e" stroke-width="1.2" opacity=".38" />
|
|
90
|
+
<path d="m-18-7 44-2v18l-44 2Z" fill="#f2e0b9" /><text x="4" y="5" text-anchor="middle" font-family="ui-monospace, SFMono-Regular, Consolas, monospace" font-size="10.8" fill="#765c40">packet</text>
|
|
91
|
+
<path d="m27 27 11-1m-11 4 11-1m-11 4 7-.5" fill="none" stroke="#8d653f" stroke-width="1.4" opacity=".65" />
|
|
92
|
+
</g>
|
|
93
|
+
</g>
|
|
94
|
+
<g data-part="monkey" transform="translate(190 536)">
|
|
95
|
+
<g data-part="leg-back"><path d="M-36-67-10-65-7-34-19-7-42-7-40-20-26-39Z" fill="#8e6144" /><path d="M-40-17Q-50-15-52-5L-46 0-12 0-12-9-27-15Z" fill="#c49468" /><path d="m-42-7 0 6m9-7 0 7" stroke="#8e6144" stroke-width="1.5" stroke-linecap="round" /></g>
|
|
96
|
+
<g data-part="leg-front"><path d="M7-67 35-63 36-37 24-10 4-10 12-39Z" fill="#ad7951" /><path d="M17-17Q25-20 35-13L49-8 49-1 11 0 5-7Z" fill="#d0a575" /><path d="m34-8 0 7m8-6 0 6" stroke="#9d704d" stroke-width="1.5" stroke-linecap="round" /></g>
|
|
97
|
+
<g data-part="monkey-upper">
|
|
98
|
+
<g data-part="tail"><path d="M-35-68C-75-52-118-62-122-105-125-146-86-164-68-141-51-118-80-103-89-124" fill="none" stroke="#8e6144" stroke-width="18" stroke-linecap="round" /><path d="M-40-69C-79-58-116-68-117-106-118-139-88-153-74-138" fill="none" stroke="#bd8b5f" stroke-width="7" stroke-linecap="round" /></g>
|
|
99
|
+
<path d="M-29-144Q-48-136-48-103L-41-66Q-23-45 4-48 30-47 42-69L38-123Q31-145 12-149Z" fill="#ad7951" />
|
|
100
|
+
<path d="M-29-144-39-110-33-74-16-52-41-66Q-54-109-45-129Z" fill="#8e6144" /><path d="m12-149 26 26 4 54-17 17 2-37-11-40Z" fill="#c49468" />
|
|
101
|
+
<path d="M-10-132Q-30-120-27-94L-18-67Q1-55 22-72L27-102Q23-129 8-135Z" fill="#e4be88" /><path d="M-10-132-15-102-8-77 8-63-18-67-27-94Q-30-120-10-132Z" fill="#d0a575" />
|
|
102
|
+
<path d="m-34-91 15 9-5 15m45-37 11 10" fill="none" stroke="#e0b17a" stroke-width="1.3" opacity=".65" />
|
|
103
|
+
<path d="M-25-148-7-156 26-147 28-132-8-127-28-137Z" fill="#78958a" /><path d="m-27-139 18 5-13 33-11-10Z" fill="#8da89a" /><path d="m-6-139 31-7 3 14-36 5Z" fill="#5e7c70" />
|
|
104
|
+
<g data-part="arm-back"><path data-part="arm-back-main" d="M-32-128Q-48-89 31-78" fill="none" stroke="#8e6144" stroke-width="23" stroke-linecap="round" stroke-linejoin="round" /><path data-part="arm-back-fold" d="M-32-130-32-96 31-82" fill="none" stroke="#ad7951" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" /></g>
|
|
105
|
+
<g data-part="monkey-head">
|
|
106
|
+
<path d="M-44-209C-79-225-91-189-70-171L-46-169Z" fill="#ad7951" /><path d="M-52-201C-72-211-78-189-63-180L-48-179Z" fill="#deb280" />
|
|
107
|
+
<path d="M44-209C77-224 88-188 67-171L45-170Z" fill="#b98559" /><path d="M52-201C71-210 76-188 62-180L48-180Z" fill="#e4be88" />
|
|
108
|
+
<path d="M-47-210Q-50-237-22-243L-12-255-1-246 14-249 27-239Q52-231 50-207L49-178Q46-150 22-142L-14-143Q-44-151-48-176Z" fill="#ad7951" />
|
|
109
|
+
<path d="M-47-210Q-50-237-22-243L-12-255-1-246-19-228-28-190-18-149-14-143Q-44-151-48-176Z" fill="#8e6144" />
|
|
110
|
+
<path d="m-1-246 15-3 13 10Q52-231 50-207L40-194 29-219 6-230-19-228Z" fill="#c49468" />
|
|
111
|
+
<path d="M0-216C-23-239-51-217-39-193-59-174-36-146-8-149L12-149C40-146 58-173 39-192 48-219 22-238 0-216Z" fill="#f1d9b1" />
|
|
112
|
+
<path d="M-39-193C-53-177-38-153-14-150L-8-149-23-163-27-186Q-40-218-18-225C-41-225-48-211-39-193Z" fill="#e4be88" />
|
|
113
|
+
<g data-part="face">
|
|
114
|
+
<path data-part="brow-left" d="M-29-211Q-20-216-12-212" fill="none" stroke="#805c42" stroke-width="3" stroke-linecap="round" /><path data-part="brow-right" d="M12-212Q21-216 29-210" fill="none" stroke="#805c42" stroke-width="3" stroke-linecap="round" />
|
|
115
|
+
<g data-part="eye-left"><ellipse cx="-20" cy="-194" rx="9" ry="11.5" fill="#fff5dc" /><ellipse data-part="pupil-left" cx="-17" cy="-193" rx="4.5" ry="6.7" fill="#493e33" /><circle cx="-15.6" cy="-196" r="1.5" fill="#fff7e8" /></g>
|
|
116
|
+
<g data-part="eye-right"><ellipse cx="20" cy="-194" rx="9" ry="11.5" fill="#fff5dc" /><ellipse data-part="pupil-right" cx="23" cy="-193" rx="4.5" ry="6.7" fill="#493e33" /><circle cx="24.4" cy="-196" r="1.5" fill="#fff7e8" /></g>
|
|
117
|
+
<path d="M-5-180Q0-184 5-180L3-175-3-175Z" fill="#9e7352" />
|
|
118
|
+
<path data-part="mouth" d="M-10-165Q0-157 11-164" fill="none" stroke="#76503b" stroke-width="2.8" stroke-linecap="round" />
|
|
119
|
+
<path data-part="tongue" d="M-6-154Q0-160 6-154" fill="none" stroke="#c99177" stroke-width="4" opacity="0" stroke-linecap="round" />
|
|
120
|
+
<path d="m-33-173 6 2m54-2 6-2" fill="none" stroke="#d5ac7c" stroke-width="2" stroke-linecap="round" />
|
|
121
|
+
</g>
|
|
122
|
+
</g>
|
|
123
|
+
<g data-part="hand-back" transform="translate(31 -78) rotate(-5)"><path d="M-10-6Q-12-14-6-14L8-12Q16-10 14-3L10 5Q4 12-6 8L-12 4Z" fill="#deb280" /><path d="m-6-7 13 2m-14 3 13 2" stroke="#a57550" stroke-width="1.2" stroke-linecap="round" fill="none" /></g>
|
|
124
|
+
<g data-part="panic-lines" opacity="0" fill="none" stroke="#ae8154" stroke-width="2.6" stroke-linecap="round"><path d="m-69-245-9-8m22-1-4-10m132 22 8-8m-24-1 2-11" /></g>
|
|
125
|
+
<g data-part="sweat" opacity="0"><path d="M0 0Q-15 16-2 20 12 21 0 0Z" fill="#8eb5b2" /><path d="m-1 9-3 6" fill="none" stroke="#d6e4d5" stroke-width="2" stroke-linecap="round" /></g>
|
|
126
|
+
</g>
|
|
127
|
+
</g>
|
|
128
|
+
<g data-part="port-rim"><path d="m662 385 3 2v59l-3-2Z" fill="var(--fire-metal-light)" /><path d="m644 446 49 27v5l-49-27Z" fill="var(--fire-metal-dark)" /></g>
|
|
129
|
+
<g data-part="fire" opacity="0">
|
|
130
|
+
<g data-flame="0"><path class="flame-outer" fill="#d77e3e" /><path class="flame-inner" fill="#f2bc60" /><path class="flame-core" fill="#f8dda0" /></g>
|
|
131
|
+
<g data-flame="1"><path class="flame-outer" fill="#c86b38" /><path class="flame-inner" fill="#eaa34d" /><path class="flame-core" fill="#f6d68a" /></g>
|
|
132
|
+
<g data-flame="2"><path class="flame-outer" fill="#df8942" /><path class="flame-inner" fill="#f4c469" /><path class="flame-core" fill="#f9e2aa" /></g>
|
|
133
|
+
<g data-flame="3"><path class="flame-outer" fill="#d77e3e" /><path class="flame-inner" fill="#f2bc60" /><path class="flame-core" fill="#f8dda0" /></g>
|
|
134
|
+
<g data-flame="4"><path class="flame-outer" fill="#d17438" /><path class="flame-inner" fill="#edb255" /><path class="flame-core" fill="#f8dda0" /></g>
|
|
135
|
+
<g data-flame="5"><path class="flame-outer" fill="#df8942" /><path class="flame-inner" fill="#f4c469" /><path class="flame-core" fill="#f9e2aa" /></g>
|
|
136
|
+
<g data-flame="6"><path class="flame-outer" fill="#c86b38" /><path class="flame-inner" fill="#eaa34d" /><path class="flame-core" fill="#f6d68a" /></g>
|
|
137
|
+
</g>
|
|
138
|
+
<g data-part="embers" fill="#efb457" opacity="0"><path data-ember="0" d="m0-5 3 5-3 5-3-5Z" /><path data-ember="1" d="m0-4 2 4-2 4-2-4Z" /><path data-ember="2" d="m0-5 3 5-3 5-3-5Z" /><path data-ember="3" d="m0-4 2 4-2 4-2-4Z" /><path data-ember="4" d="m0-5 3 5-3 5-3-5Z" /><path data-ember="5" d="m0-4 2 4-2 4-2-4Z" /><path data-ember="6" d="m0-5 3 5-3 5-3-5Z" /><path data-ember="7" d="m0-4 2 4-2 4-2-4Z" /></g>
|
|
139
|
+
<g data-part="blast" opacity="0" transform="translate(778 367)">
|
|
140
|
+
<path d="m0-127 24 56 54-25-8 54 63 4-39 42 42 39-64 4 10 59-57-26-25 61-24-62-58 27 8-58-64-4 42-42-39-39 61-8-7-53 53 29Z" fill="#e5a44e" /><path d="m0-88 18 44 37-17-5 37 45 3-29 30 30 25-45 3 6 39-39-17-18 40-16-42-40 18 6-38-44-4 29-28-27-28 42-4-4-37 37 20Z" fill="#f7dc96" />
|
|
141
|
+
</g>
|
|
142
|
+
<g data-part="debris" opacity="0"><path data-debris="0" d="m-10-5 18-2 4 9-20 3Z" fill="var(--fire-metal-dark)" /><path data-debris="1" d="m-8-7 16 4-3 10-14-4Z" fill="var(--fire-metal-light)" /><path data-debris="2" d="m-5-6 12 1 2 10-10 2Z" fill="#b49c70" /><path data-debris="3" d="m-8-4 15-2 4 8-16 3Z" fill="var(--fire-panel-front)" /><path data-debris="4" d="m-6-8 10 3-2 13-9-4Z" fill="#9a8260" /></g>
|
|
143
|
+
<path d="M-1000-800H2000V1200H-1000Z" :fill="`url(#${uid}-grain)`" pointer-events="none" />
|
|
144
|
+
</g>
|
|
145
|
+
<path d="M40 78 43 581Q44 614 80 613" fill="none" stroke="#fff8de" stroke-width="2" opacity=".35" />
|
|
146
|
+
</g>
|
|
147
|
+
</svg>
|
|
148
|
+
|
|
149
|
+
</div>
|
|
150
|
+
<div class="fire-scene__controls">
|
|
151
|
+
<button class="fire-scene__control" type="button" aria-label="重新播放动画" @click="replay"><RotateCcw :size="13" aria-hidden="true" /><span>重播</span></button>
|
|
152
|
+
<button v-if="canAnimate" class="fire-scene__control" type="button" :aria-label="userPaused ? '继续动画' : '暂停动画'" :aria-pressed="userPaused" @click="userPaused = !userPaused"><Play v-if="userPaused" :size="13" aria-hidden="true" /><Pause v-else :size="13" aria-hidden="true" /></button>
|
|
153
|
+
</div>
|
|
154
|
+
</figure>
|
|
155
|
+
</template>
|
|
156
|
+
|
|
157
|
+
<script setup lang="ts">
|
|
158
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch } from 'vue'
|
|
159
|
+
import { Pause, Play, RotateCcw } from 'lucide-vue-next'
|
|
160
|
+
import { useApple } from 'apptify'
|
|
161
|
+
import { resolveMotion } from 'apptify'
|
|
162
|
+
import { createServerFireStory, type ServerFirePhase, type ServerFireStory } from './server-fire-paper-story'
|
|
163
|
+
|
|
164
|
+
const uid = `server-fire-${useId().replace(/:/g, '')}`
|
|
165
|
+
const apple = useApple()
|
|
166
|
+
const dark = computed(() => apple.theme.value.current.scheme === 'dark')
|
|
167
|
+
const scene = ref<HTMLElement>()
|
|
168
|
+
const art = ref<SVGSVGElement>()
|
|
169
|
+
const media = typeof window === 'undefined' ? undefined : window.matchMedia('(prefers-reduced-motion: reduce)')
|
|
170
|
+
const systemReduced = ref(media?.matches ?? false)
|
|
171
|
+
const canAnimate = computed(() => resolveMotion('inherit', apple.motion.value.mode, apple.motion.value.reduced || systemReduced.value) === 'full')
|
|
172
|
+
const userPaused = ref(false)
|
|
173
|
+
const hidden = ref(typeof document !== 'undefined' && document.hidden)
|
|
174
|
+
const offscreen = ref(false)
|
|
175
|
+
const suspended = computed(() => userPaused.value || hidden.value || offscreen.value)
|
|
176
|
+
const phase = ref<ServerFirePhase>('delivery')
|
|
177
|
+
let player: ServerFireStory | undefined
|
|
178
|
+
let observer: IntersectionObserver | undefined
|
|
179
|
+
let mounted = false
|
|
180
|
+
|
|
181
|
+
function replay() { userPaused.value = false; player?.replay() }
|
|
182
|
+
function visibilityChanged() { hidden.value = document.hidden }
|
|
183
|
+
function motionChanged() { systemReduced.value = media?.matches ?? false }
|
|
184
|
+
watch(suspended, paused => player?.setPaused(paused), { flush: 'sync' })
|
|
185
|
+
watch(canAnimate, enabled => player?.setReduced(!enabled), { flush: 'post' })
|
|
186
|
+
onMounted(async () => {
|
|
187
|
+
mounted = true
|
|
188
|
+
document.addEventListener('visibilitychange', visibilityChanged)
|
|
189
|
+
media?.addEventListener('change', motionChanged)
|
|
190
|
+
if (typeof IntersectionObserver !== 'undefined' && scene.value) {
|
|
191
|
+
observer = new IntersectionObserver(entries => { if (entries[0]) offscreen.value = !entries[0].isIntersecting }, { threshold: 0 })
|
|
192
|
+
observer.observe(scene.value)
|
|
193
|
+
}
|
|
194
|
+
await nextTick()
|
|
195
|
+
if (!mounted || !art.value) return
|
|
196
|
+
player = createServerFireStory(art.value, { reduced: !canAnimate.value, onPhase: value => { phase.value = value } })
|
|
197
|
+
player.setPaused(suspended.value)
|
|
198
|
+
})
|
|
199
|
+
onBeforeUnmount(() => {
|
|
200
|
+
mounted = false
|
|
201
|
+
player?.dispose()
|
|
202
|
+
observer?.disconnect()
|
|
203
|
+
media?.removeEventListener('change', motionChanged)
|
|
204
|
+
document.removeEventListener('visibilitychange', visibilityChanged)
|
|
205
|
+
})
|
|
206
|
+
</script>
|
|
207
|
+
|
|
208
|
+
<style scoped>
|
|
209
|
+
.fire-scene {
|
|
210
|
+
--fire-wall: #f1e8d5; --fire-panel: #d9dcc1; --fire-sidewall: #dddcc6;
|
|
211
|
+
--fire-floor: #d8c6a3; --fire-floor-line: #ad9574; --fire-baseboard: #b9b698;
|
|
212
|
+
--fire-frame: #d4cbbb; --fire-window-frame: #e2d3b0; --fire-window-dark: #c1b38f;
|
|
213
|
+
--fire-window-sky: #e4e6ce; --fire-sun: #fff3cf; --fire-hill-far: #bdcbb0;
|
|
214
|
+
--fire-hill-fold: #d1d7b9; --fire-hill-near: #9eb58f; --fire-leaf-dark: #6c896c;
|
|
215
|
+
--fire-leaf-light: #94a77a; --fire-pot: #bd9170; --fire-pot-light: #d1a985;
|
|
216
|
+
--fire-pot-dark: #876d50; --fire-metal: #7d9287; --fire-metal-light: #a8b5a1;
|
|
217
|
+
--fire-metal-pale: #c4cbb1; --fire-metal-dark: #526e63; --fire-panel-front: #637f73;
|
|
218
|
+
--fire-vent: #3e594e; --fire-cavity: #354a3d; --fire-label: #e6e6cc;
|
|
219
|
+
--fire-lamp: #f4e7bb; --fire-cable: #69765f; --fire-conduit: #a7b09b;
|
|
220
|
+
--fire-conduit-light: #d4d6bc; --fire-conduit-dark: #899884;
|
|
221
|
+
--fire-smoke: #b6b5a3; --fire-smoke-light: #d2cebb; --fire-ground-shadow: #64563d;
|
|
222
|
+
position: relative; min-width: 0; margin: 0;
|
|
223
|
+
}
|
|
224
|
+
.fire-scene--dark {
|
|
225
|
+
--fire-wall: #293b3b; --fire-panel: #3c5147; --fire-sidewall: #263936;
|
|
226
|
+
--fire-floor: #7b7a5d; --fire-floor-line: #565e46; --fire-baseboard: #586951;
|
|
227
|
+
--fire-frame: #142224; --fire-window-frame: #85977b; --fire-window-dark: #526853;
|
|
228
|
+
--fire-window-sky: #334e53; --fire-sun: #e2d6a2; --fire-hill-far: #506d5e;
|
|
229
|
+
--fire-hill-fold: #637d64; --fire-hill-near: #3e5f4a; --fire-leaf-dark: #314e3c;
|
|
230
|
+
--fire-leaf-light: #739365; --fire-pot: #a37e5c; --fire-pot-light: #b4946e;
|
|
231
|
+
--fire-pot-dark: #69563e; --fire-metal: #648477; --fire-metal-light: #8da694;
|
|
232
|
+
--fire-metal-pale: #b9c5a6; --fire-metal-dark: #345b4d; --fire-panel-front: #496e5e;
|
|
233
|
+
--fire-vent: #24493a; --fire-cavity: #1c3027; --fire-label: #dee2c6;
|
|
234
|
+
--fire-lamp: #e7d498; --fire-cable: #b2b18d; --fire-conduit: #647c69;
|
|
235
|
+
--fire-conduit-light: #8c9f84; --fire-conduit-dark: #405b4c;
|
|
236
|
+
--fire-smoke: #839486; --fire-smoke-light: #a5b19b; --fire-ground-shadow: #142920;
|
|
237
|
+
}
|
|
238
|
+
.fire-scene__window { overflow: hidden; padding: 4px; }
|
|
239
|
+
.fire-scene__art { display: block; width: 100%; height: auto; overflow: visible; }
|
|
240
|
+
.fire-scene__controls { display: flex; align-items: center; justify-content: flex-end; gap: 2px; margin: -3px 1.5% 0 0; }
|
|
241
|
+
.fire-scene__control { display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-height: 36px; min-width: 36px; padding: 6px 10px; color: var(--apple-secondary, #73796b); background: transparent; border: 0; border-radius: 18px; font: inherit; font-size: 11px; cursor: pointer; }
|
|
242
|
+
.fire-scene__control:hover { background: var(--apple-hover, #71816b12); }
|
|
243
|
+
.fire-scene__control:focus-visible { outline: 2px solid var(--apple-accent, #6f8e79); outline-offset: 3px; }
|
|
244
|
+
.fire-scene__control svg { width: 13px; height: 13px; }
|
|
245
|
+
.fire-scene__control[hidden] { display: none; }
|
|
246
|
+
@media (max-width: 880px) { .fire-scene__window { margin-inline: -10px; } }
|
|
247
|
+
@media (pointer: coarse) { .fire-scene__control { min-height: 44px; min-width: 44px; } }
|
|
248
|
+
|
|
249
|
+
</style>
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
export type ServerFirePhase = 'delivery' | 'reveal' | 'explosion' | 'aftermath'
|
|
2
|
+
|
|
3
|
+
export interface ServerFireStory {
|
|
4
|
+
setPaused(paused: boolean): void
|
|
5
|
+
setReduced(reduced: boolean): void
|
|
6
|
+
replay(): void
|
|
7
|
+
seek(seconds: number): void
|
|
8
|
+
getTime(): number
|
|
9
|
+
dispose(): void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const TAU = Math.PI * 2
|
|
13
|
+
const EXPLOSION = 5.4
|
|
14
|
+
const AFTERMATH = 7.2
|
|
15
|
+
const clamp = (n: number, lo = 0, hi = 1) => Math.min(hi, Math.max(lo, n))
|
|
16
|
+
const mix = (a: number, b: number, p: number) => a + (b - a) * p
|
|
17
|
+
const smooth = (p: number) => { const u = clamp(p); return u * u * (3 - 2 * u) }
|
|
18
|
+
const during = (t: number, a: number, b: number) => smooth((t - a) / (b - a))
|
|
19
|
+
type Point = [number, number]
|
|
20
|
+
const point = (a: Point, b: Point, p: number): Point => [mix(a[0], b[0], p), mix(a[1], b[1], p)]
|
|
21
|
+
const f = (value: number) => Number(value.toFixed(3))
|
|
22
|
+
|
|
23
|
+
export function createServerFireRenderer(svg: SVGSVGElement) {
|
|
24
|
+
const parts = new Map<string, SVGElement>()
|
|
25
|
+
svg.querySelectorAll<SVGElement>('[data-part]').forEach(el => parts.set(el.dataset.part!, el))
|
|
26
|
+
const part = (name: string) => {
|
|
27
|
+
const el = parts.get(name)
|
|
28
|
+
if (!el) throw new Error(`Missing server-fire scene part: ${name}`)
|
|
29
|
+
return el
|
|
30
|
+
}
|
|
31
|
+
const set = (name: string, key: string, value: string | number) => {
|
|
32
|
+
const el = part(name), text = String(value)
|
|
33
|
+
if (el.getAttribute(key) !== text) el.setAttribute(key, text)
|
|
34
|
+
}
|
|
35
|
+
const transform = (name: string, value: string) => set(name, 'transform', value)
|
|
36
|
+
const opacity = (name: string, value: number) => set(name, 'opacity', f(clamp(value)))
|
|
37
|
+
const smoke = [...svg.querySelectorAll<SVGElement>('[data-smoke]')]
|
|
38
|
+
const embers = [...svg.querySelectorAll<SVGElement>('[data-ember]')]
|
|
39
|
+
const debris = [...svg.querySelectorAll<SVGElement>('[data-debris]')]
|
|
40
|
+
const leds = [...svg.querySelectorAll<SVGElement>('[data-led]')]
|
|
41
|
+
const flames = [...svg.querySelectorAll<SVGElement>('[data-flame]')].map(el => ({
|
|
42
|
+
el,
|
|
43
|
+
outer: el.querySelector<SVGPathElement>('.flame-outer')!,
|
|
44
|
+
inner: el.querySelector<SVGPathElement>('.flame-inner')!,
|
|
45
|
+
core: el.querySelector<SVGPathElement>('.flame-core')!,
|
|
46
|
+
}))
|
|
47
|
+
const fireSources = [[734, 431, 27, 92], [780, 434, 32, 119], [822, 435, 25, 84], [700, 199, 29, 90], [746, 192, 33, 117], [791, 199, 28, 102], [836, 222, 22, 82]]
|
|
48
|
+
|
|
49
|
+
function arm(side: 'back' | 'front', shoulder: Point, elbow: Point, hand: Point, angle: number) {
|
|
50
|
+
const path = `M${f(shoulder[0])} ${f(shoulder[1])}L${f(elbow[0])} ${f(elbow[1])}L${f(hand[0])} ${f(hand[1])}`
|
|
51
|
+
set(`arm-${side}-main`, 'd', path)
|
|
52
|
+
set(`arm-${side}-fold`, 'd', `M${f(shoulder[0] + 2)} ${f(shoulder[1] - 4)}L${f(elbow[0] + 2)} ${f(elbow[1] - 4)}L${f(hand[0])} ${f(hand[1] - 3)}`)
|
|
53
|
+
transform(`hand-${side}`, `translate(${f(hand[0])} ${f(hand[1])}) rotate(${f(angle)})`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function flamePath(w: number, h: number, sway: number) {
|
|
57
|
+
return `M${f(-w * .72)} 2C${f(-w * 1.1)} ${f(-h * .26)} ${f(-w * .34)} ${f(-h * .35)} ${f(-w * .56)} ${f(-h * .67)}L${f(-w * .1)} ${f(-h * .49)}Q${f(w * .36 + sway)} ${f(-h * .72)} ${f(sway)} ${f(-h)}C${f(w * 1.2 + sway)} ${f(-h * .71)} ${f(w * .27)} ${f(-h * .44)} ${f(w * .69)} ${f(-h * .59)}Q${f(w * 1.2)} ${f(-h * .19)} ${f(w * .64)} 2Z`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function render(seconds: number): ServerFirePhase {
|
|
61
|
+
const t = Math.max(0, seconds)
|
|
62
|
+
const phase: ServerFirePhase = t < 3.78 ? 'delivery' : t < EXPLOSION ? 'reveal' : t < AFTERMATH ? 'explosion' : 'aftermath'
|
|
63
|
+
const approach = during(t, .18, 2.5)
|
|
64
|
+
const walkX = mix(190, 524, approach)
|
|
65
|
+
const stride = (walkX - 190) / 43 * Math.PI
|
|
66
|
+
const walking = during(t, .1, .4) * (1 - during(t, 2.22, 2.53))
|
|
67
|
+
const walkLift = Math.abs(Math.sin(stride)) * 4.6 * walking
|
|
68
|
+
const backStep = during(t, EXPLOSION + .02, EXPLOSION + .79)
|
|
69
|
+
const settle = during(t, EXPLOSION + .78, EXPLOSION + 1.37)
|
|
70
|
+
const loop = Math.max(0, t - 6.55)
|
|
71
|
+
const idle = during(t, 6.55, 7.25)
|
|
72
|
+
const jump = t < EXPLOSION ? 0 : 32 * Math.sin(Math.PI * clamp((t - EXPLOSION) / .76))
|
|
73
|
+
const monkeyX = walkX - 111 * backStep + 20 * settle
|
|
74
|
+
const monkeyY = 536 - walkLift - jump
|
|
75
|
+
const monkeyPlacement = `translate(${f(monkeyX)} ${f(monkeyY)})`
|
|
76
|
+
transform('monkey', monkeyPlacement)
|
|
77
|
+
transform('monkey-back', monkeyPlacement)
|
|
78
|
+
transform('monkey-shadow', `translate(${f(monkeyX - 190)} 0)`)
|
|
79
|
+
set('monkey-shadow', 'ry', f(12 * (1 - jump / 95)))
|
|
80
|
+
set('monkey-shadow', 'opacity', f(.17 - jump / 420))
|
|
81
|
+
const recoil = Math.sin(Math.PI * clamp((t - EXPLOSION) / 1.1))
|
|
82
|
+
const breath = 1.1 * Math.sin(loop * TAU / 2.5) * idle
|
|
83
|
+
const tremble = (Math.sin(loop * 18) * .4 + Math.sin(loop * 10.5) * .65) * idle
|
|
84
|
+
const bodyAngle = Math.sin(stride) * 1.4 * walking - recoil * 10 + tremble * .55
|
|
85
|
+
const upperPlacement = `translate(${f(tremble)} ${f(breath)}) rotate(${f(bodyAngle)} 0 -55)`
|
|
86
|
+
transform('monkey-upper', upperPlacement)
|
|
87
|
+
transform('monkey-back-upper', upperPlacement)
|
|
88
|
+
transform('leg-back', `rotate(${f(Math.sin(stride) * 17 * walking - recoil * 13)} -20 -48)`)
|
|
89
|
+
transform('leg-front', `rotate(${f(-Math.sin(stride) * 17 * walking + recoil * 20)} 20 -48)`)
|
|
90
|
+
transform('tail', `rotate(${f(Math.sin(stride * .5) * 5 * walking + recoil * 18 + Math.sin(loop * 2.5) * 3 * idle)} -35 -68)`)
|
|
91
|
+
|
|
92
|
+
const push = during(t, 2.64, 3.29)
|
|
93
|
+
const release = during(t, 3.29, 3.94)
|
|
94
|
+
const throwHands = during(t, EXPLOSION, EXPLOSION + .23)
|
|
95
|
+
const grabHead = during(t, EXPLOSION + .25, EXPLOSION + .98)
|
|
96
|
+
const shocked = during(t, EXPLOSION, EXPLOSION + .16)
|
|
97
|
+
let backHand: Point = [31 + 54 * push, -78 - 10 * push]
|
|
98
|
+
let frontHand: Point = [108 + 23 * push, -87 - 10 * push]
|
|
99
|
+
let backElbow: Point = [-25 + 39 * push, -88 - 4 * push]
|
|
100
|
+
let frontElbow: Point = [53 + 28 * push, -87 - 8 * push]
|
|
101
|
+
backHand = point(backHand, [-43, -70], release)
|
|
102
|
+
frontHand = point(frontHand, [48, -72], release)
|
|
103
|
+
backElbow = point(backElbow, [-49, -99], release)
|
|
104
|
+
frontElbow = point(frontElbow, [53, -100], release)
|
|
105
|
+
backHand = point(backHand, [-94, -173], throwHands)
|
|
106
|
+
frontHand = point(frontHand, [105, -168], throwHands)
|
|
107
|
+
backElbow = point(backElbow, [-70, -135], throwHands)
|
|
108
|
+
frontElbow = point(frontElbow, [80, -130], throwHands)
|
|
109
|
+
|
|
110
|
+
const headAngle = (1 - shocked) * (2.5 * walking + during(t, 4, 4.35) * -3) + shocked * (-5 * recoil + Math.sin(loop * 3.1) * 1.7 * idle)
|
|
111
|
+
const headX = Math.sin(loop * 7.2) * .55 * idle
|
|
112
|
+
const headY = -2.2 * shocked + Math.sin(loop * 3.7) * .65 * idle
|
|
113
|
+
transform('monkey-head', `translate(${f(headX)} ${f(headY)}) rotate(${f(headAngle)} 0 -188)`)
|
|
114
|
+
const headAnchor = (x: number, y: number): Point => {
|
|
115
|
+
const a = headAngle * Math.PI / 180
|
|
116
|
+
return [x * Math.cos(a) - (y + 188) * Math.sin(a) + headX, x * Math.sin(a) + (y + 188) * Math.cos(a) - 188 + headY]
|
|
117
|
+
}
|
|
118
|
+
// Both palms follow the head's exact transform, including its idle motion.
|
|
119
|
+
backHand = point(backHand, headAnchor(-42, -222), grabHead)
|
|
120
|
+
frontHand = point(frontHand, headAnchor(42, -222), grabHead)
|
|
121
|
+
backElbow = point(backElbow, [-88 - Math.sin(loop * 4) * 1.2 * idle, -165 + breath], grabHead)
|
|
122
|
+
frontElbow = point(frontElbow, [88 + Math.sin(loop * 4) * 1.2 * idle, -165 + breath], grabHead)
|
|
123
|
+
arm('back', [-32, -128], backElbow, backHand, mix(-5, -51 + headAngle, grabHead))
|
|
124
|
+
arm('front', [33, -128], frontElbow, frontHand, mix(-12, 48 + headAngle, grabHead))
|
|
125
|
+
const look = mix(3, 1.2 + Math.sin(loop * 1.1) * .7, shocked)
|
|
126
|
+
set('pupil-left', 'cx', f(-20 + look)); set('pupil-right', 'cx', f(20 + look))
|
|
127
|
+
set('pupil-left', 'cy', f(-193 - shocked)); set('pupil-right', 'cy', f(-193 - shocked))
|
|
128
|
+
const blink = t > 8 ? Math.max(0, 1 - Math.abs((t % 4.9) - 4.7) / .11) : 0
|
|
129
|
+
transform('eye-left', `translate(-20 -194) scale(1 ${f(1 - .89 * blink)}) translate(20 194)`)
|
|
130
|
+
transform('eye-right', `translate(20 -194) scale(1 ${f(1 - .89 * blink)}) translate(-20 194)`)
|
|
131
|
+
set('brow-left', 'd', `M-29 ${f(-211 - 5 * shocked)}Q-20 ${f(-216 - 7 * shocked)}-12 ${f(-212 - 5 * shocked)}`)
|
|
132
|
+
set('brow-right', 'd', `M12 ${f(-212 - 5 * shocked)}Q21 ${f(-216 - 7 * shocked)} 29 ${f(-210 - 5 * shocked)}`)
|
|
133
|
+
const mouthH = 7.4 * shocked
|
|
134
|
+
set('mouth', 'd', shocked < .02 ? 'M-10-165Q0-157 11-164' : `M-9-163C-9 ${f(-160 - 2 * mouthH)} 9 ${f(-160 - 2 * mouthH)} 9-163C9 ${f(-154 + .6 * mouthH)}-9 ${f(-154 + .6 * mouthH)}-9-163Z`)
|
|
135
|
+
set('mouth', 'fill', shocked > .02 ? '#624533' : 'none')
|
|
136
|
+
set('mouth', 'stroke-width', mix(1.7, 1, shocked))
|
|
137
|
+
opacity('tongue', shocked)
|
|
138
|
+
opacity('panic-lines', shocked * (.35 + .2 * (1 + Math.sin(loop * 7))))
|
|
139
|
+
const sweatAge = ((Math.max(0, t - 6) % 3.1) / 3.1)
|
|
140
|
+
transform('sweat', `translate(${f(65 + sweatAge * 10)} ${f(-224 + sweatAge * 44)}) rotate(-19) scale(${f(.55 + sweatAge * .2)})`)
|
|
141
|
+
opacity('sweat', t > 6 ? Math.sin(Math.PI * sweatAge) * .85 : 0)
|
|
142
|
+
|
|
143
|
+
// The server's intake occludes the box; opacity never substitutes for insertion.
|
|
144
|
+
const pull = during(t, 3.29, 3.8)
|
|
145
|
+
const packetX = walkX + 75 + 54 * push + 70 * pull
|
|
146
|
+
const packetY = 427 - walkLift - 9 * push
|
|
147
|
+
transform('packet', `translate(${f(packetX)} ${f(packetY)}) rotate(${f(Math.sin(stride) * 1.2 * walking)})`)
|
|
148
|
+
set('packet', 'visibility', t >= 3.8 ? 'hidden' : 'visible')
|
|
149
|
+
|
|
150
|
+
const reveal = during(t, 3.78, 5.16)
|
|
151
|
+
const scale = mix(1.8, 1, reveal)
|
|
152
|
+
const cameraX = mix(walkX + 65, 500, reveal)
|
|
153
|
+
const cameraY = mix(412, 330, reveal)
|
|
154
|
+
const impactAge = Math.max(0, t - EXPLOSION)
|
|
155
|
+
const cameraShake = t >= EXPLOSION && impactAge < .42 ? Math.sin(impactAge * 65) * 3.5 * (1 - impactAge / .42) : 0
|
|
156
|
+
transform('camera', `translate(${f(500 - scale * cameraX + cameraShake)} ${f(330 - scale * cameraY + cameraShake * .3)}) scale(${f(scale)})`)
|
|
157
|
+
|
|
158
|
+
const warning = during(t, 3.98, 5.38) * (t < EXPLOSION ? 1 : 0)
|
|
159
|
+
const shake = Math.sin(t * 49) * warning * 1.8 + (t >= EXPLOSION ? Math.sin(impactAge * 39) * Math.exp(-impactAge * 7) * 3 : 0)
|
|
160
|
+
transform('server', `translate(${f(shake)} ${f(shake * .3)})`)
|
|
161
|
+
transform('port-rim', `translate(${f(shake)} ${f(shake * .3)})`)
|
|
162
|
+
leds.forEach((led, i) => {
|
|
163
|
+
led.setAttribute('fill', t >= EXPLOSION ? '#635b42' : warning > .08 ? '#dca357' : '#b8ce8b')
|
|
164
|
+
led.setAttribute('opacity', String(t >= EXPLOSION ? .7 : .55 + .45 * (.5 + .5 * Math.sin(t * (warning ? 16 : 3.8) + i * 2.2))))
|
|
165
|
+
})
|
|
166
|
+
const lid = during(t, EXPLOSION, EXPLOSION + .24)
|
|
167
|
+
const lidOvershoot = Math.sin(Math.PI * during(t, EXPLOSION, EXPLOSION + .72))
|
|
168
|
+
transform('server-lid', `rotate(${f(-9 * lid - 13 * lidOvershoot)} 650 180)`)
|
|
169
|
+
const door = during(t, EXPLOSION, EXPLOSION + .95)
|
|
170
|
+
transform('server-door', `translate(${f(36 * door)} ${f(133 * door - Math.sin(Math.PI * door) * 43)}) rotate(${f(23 * door)} 780 380)`)
|
|
171
|
+
opacity('server-damage', t >= EXPLOSION ? 1 : 0)
|
|
172
|
+
transform('rack-upper', `rotate(${f(2.5 * lid)} 717 261)`)
|
|
173
|
+
transform('rack-lower', `rotate(${f(-2 * lid)} 717 497)`)
|
|
174
|
+
transform('wire-one', `rotate(${f(Math.sin(loop * 3.3) * 2 * idle)} 754 348)`)
|
|
175
|
+
transform('wire-two', `rotate(${f(Math.sin(loop * 2.4 + 1) * 2 * idle)} 806 367)`)
|
|
176
|
+
|
|
177
|
+
const blastP = clamp(impactAge / .52)
|
|
178
|
+
const blastScale = t < EXPLOSION ? 0 : Math.sin(Math.PI * blastP) * (1.15 + blastP * .2)
|
|
179
|
+
transform('blast', `translate(778 367) rotate(${f(blastP * 12)}) scale(${f(Math.max(.001, blastScale))})`)
|
|
180
|
+
opacity('blast', t >= EXPLOSION && impactAge < .52 ? 1 : 0)
|
|
181
|
+
const fireBuild = during(t, EXPLOSION + .12, EXPLOSION + .65)
|
|
182
|
+
opacity('fire', fireBuild)
|
|
183
|
+
opacity('fire-glow', fireBuild * (.17 + Math.sin(t * 5.1) * .03))
|
|
184
|
+
flames.forEach(({ el, outer, inner, core }, i) => {
|
|
185
|
+
const [x, y, w, h] = fireSources[i]
|
|
186
|
+
const wave = t * (4.3 + i * .16) + i * 1.9
|
|
187
|
+
const height = h * (.9 + .12 * Math.sin(wave) + .065 * Math.sin(wave * 1.71))
|
|
188
|
+
const width = w * (.95 + .08 * Math.sin(wave + 1.2))
|
|
189
|
+
const sway = Math.sin(wave * .81) * width * .33
|
|
190
|
+
el.setAttribute('transform', `translate(${x} ${y}) scale(${f(.12 + fireBuild * .88)})`)
|
|
191
|
+
outer.setAttribute('d', flamePath(width, height, sway))
|
|
192
|
+
inner.setAttribute('d', flamePath(width * .58, height * .69, sway * .7))
|
|
193
|
+
core.setAttribute('d', flamePath(width * .25, height * .35, sway * .3))
|
|
194
|
+
})
|
|
195
|
+
opacity('smoke', fireBuild)
|
|
196
|
+
smoke.forEach((el, i) => {
|
|
197
|
+
const age = t - EXPLOSION - .1 - i * .34
|
|
198
|
+
const p = ((Math.max(0, age) % 3.9) / 3.9)
|
|
199
|
+
const x = 719 + (i % 3) * 34 + p * (24 + i * 5) + Math.sin(p * 5 + i) * 9
|
|
200
|
+
const y = 212 - p * 161
|
|
201
|
+
el.setAttribute('transform', `translate(${f(x)} ${f(y)}) rotate(${f(p * 19 - 9)}) scale(${f(.27 + p * .67)})`)
|
|
202
|
+
el.setAttribute('opacity', String(f(age < 0 ? 0 : Math.pow(Math.sin(Math.PI * p), 1.4) * .42)))
|
|
203
|
+
})
|
|
204
|
+
opacity('embers', fireBuild)
|
|
205
|
+
embers.forEach((el, i) => {
|
|
206
|
+
const age = Math.max(0, t - EXPLOSION - i * .11)
|
|
207
|
+
const p = ((age % (1.9 + i * .08)) / (1.9 + i * .08))
|
|
208
|
+
const x = 700 + (i % 4) * 40 + Math.sin(i * 2.1) * p * 40
|
|
209
|
+
const y = (i % 2 ? 370 : 195) - p * (94 + i * 7)
|
|
210
|
+
el.setAttribute('transform', `translate(${f(x)} ${f(y)}) rotate(${f(p * 120 + i * 30)}) scale(${f(1 - p * .65)})`)
|
|
211
|
+
el.setAttribute('opacity', String(f(Math.sin(Math.PI * p) * .88)))
|
|
212
|
+
})
|
|
213
|
+
opacity('debris', t >= EXPLOSION ? 1 : 0)
|
|
214
|
+
debris.forEach((el, i) => {
|
|
215
|
+
const durations = [1.17, .96, 1.09, 1.22, .88]
|
|
216
|
+
const targets = [[608, 549], [913, 553], [656, 575], [934, 528], [825, 580]]
|
|
217
|
+
const p = clamp(impactAge / durations[i])
|
|
218
|
+
const x = mix(752 + i * 13, targets[i][0], p)
|
|
219
|
+
const y = mix(359 + i * 9, targets[i][1], p) - Math.sin(Math.PI * p) * (126 - i * 13)
|
|
220
|
+
el.setAttribute('transform', `translate(${f(x)} ${f(y)}) rotate(${f(p * (i % 2 ? -385 : 309) + i * 31)})`)
|
|
221
|
+
})
|
|
222
|
+
svg.dataset.phase = phase
|
|
223
|
+
return phase
|
|
224
|
+
}
|
|
225
|
+
return { render, still: () => render(8.35) }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function createServerFireStory(
|
|
229
|
+
svg: SVGSVGElement,
|
|
230
|
+
options: { reduced?: boolean; onPhase?: (phase: ServerFirePhase) => void } = {},
|
|
231
|
+
): ServerFireStory {
|
|
232
|
+
const renderer = createServerFireRenderer(svg)
|
|
233
|
+
let time = options.reduced ? 8.35 : 0
|
|
234
|
+
let reduced = !!options.reduced
|
|
235
|
+
let paused = false
|
|
236
|
+
let disposed = false
|
|
237
|
+
let raf = 0
|
|
238
|
+
let last: number | undefined
|
|
239
|
+
let lastPhase: ServerFirePhase | undefined
|
|
240
|
+
function draw() {
|
|
241
|
+
const phase = renderer.render(time)
|
|
242
|
+
if (phase !== lastPhase) { lastPhase = phase; options.onPhase?.(phase) }
|
|
243
|
+
}
|
|
244
|
+
function frame(now: number) {
|
|
245
|
+
raf = 0
|
|
246
|
+
if (disposed || paused || reduced) { last = undefined; return }
|
|
247
|
+
if (last !== undefined) time += Math.min((now - last) / 1000, .08)
|
|
248
|
+
last = now
|
|
249
|
+
// The story clock never wraps: only the aftermath contains periodic motion.
|
|
250
|
+
draw()
|
|
251
|
+
raf = requestAnimationFrame(frame)
|
|
252
|
+
}
|
|
253
|
+
function sync() {
|
|
254
|
+
if (raf) cancelAnimationFrame(raf)
|
|
255
|
+
raf = 0; last = undefined
|
|
256
|
+
if (!disposed && !paused && !reduced) raf = requestAnimationFrame(frame)
|
|
257
|
+
}
|
|
258
|
+
draw(); sync()
|
|
259
|
+
return {
|
|
260
|
+
setPaused(value) { paused = value; sync() },
|
|
261
|
+
setReduced(value) {
|
|
262
|
+
if (reduced === value) return
|
|
263
|
+
reduced = value
|
|
264
|
+
if (value) time = Math.max(time, 8.35)
|
|
265
|
+
draw(); sync()
|
|
266
|
+
},
|
|
267
|
+
replay() { time = reduced ? 8.35 : 0; draw(); sync() },
|
|
268
|
+
seek(seconds) { if (!Number.isFinite(seconds)) return; time = Math.max(0, seconds); draw(); sync() },
|
|
269
|
+
getTime() { return time },
|
|
270
|
+
dispose() { disposed = true; if (raf) cancelAnimationFrame(raf); raf = 0; last = undefined },
|
|
271
|
+
}
|
|
272
|
+
}
|