ui/onboarding: extract cube animation to its own component

This commit is contained in:
Glen Huang
2025-09-26 21:58:32 +08:00
parent 017f0901da
commit 35f42107bb
6 changed files with 278 additions and 272 deletions

View File

@@ -1,127 +1,109 @@
div.creating {
@apply flex flex-col items-center justify-center;
.cubeConstruction {
width: 400px;
height: 400px;
perspective: 1000px;
}
.scene {
position: relative;
top: 100px;
left: 65px;
width: 200px;
height: 200px;
div.scene {
width: 400px;
height: 400px;
perspective: 1000px;
/*background: red;*/
transform: rotate3d(-1.5, -2, 0.5, 45deg);
transform-style: preserve-3d;
}
.cube {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
transform-style: preserve-3d;
}
& > .frame {
position: relative;
top: 100px;
left: 65px;
width: 200px;
height: 200px;
/*background: green;*/
.face {
position: absolute;
width: 100px;
height: 100px;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.56) 0%,
rgba(255, 255, 255, 0) 100%
);
border: 1px #10191a solid;
opacity: 1;
/*transform: rotate3d(-2, -2, 1, 45deg);*/
transform: rotate3d(-1.5, -2, 0.5, 45deg);
transform-style: preserve-3d;
&.front {
transform: rotateY(0deg) translateZ(50px);
}
& > .cube {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 200px;
transform-style: preserve-3d;
&.right {
transform: rotateY(90deg) translateZ(50px);
}
.cube-face {
position: absolute;
width: 100px;
height: 100px;
background: linear-gradient(
90deg,
rgba(255, 255, 255, 0.56) 0%,
rgba(255, 255, 255, 0) 100%
);
border: 1px #10191a solid;
&.back {
transform: rotateY(180deg) translateZ(50px);
}
opacity: 1;
&.left {
transform: rotateY(-90deg) translateZ(50px);
}
&.front {
transform: rotateY(0deg) translateZ(50px);
}
&.top {
transform: rotateX(90deg) translateZ(50px);
}
&.right {
transform: rotateY(90deg) translateZ(50px);
}
&.back {
transform: rotateY(180deg) translateZ(50px);
}
&.left {
transform: rotateY(-90deg) translateZ(50px);
}
&.top {
transform: rotateX(90deg) translateZ(50px);
}
&.bottom {
transform: rotateX(-90deg) translateZ(50px);
}
}
&.state-1 {
animation: anim-cube-1-1 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1)
infinite;
}
&.state-1-1 {
transform: translateZ(-120px);
animation: anim-cube-1-2 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1)
infinite;
}
&.state-2 {
left: 120px;
animation: anim-cube-2-1 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1)
infinite;
}
&.state-2-2 {
left: 120px;
transform: translateZ(-120px);
animation: anim-cube-2-2 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1)
infinite;
}
&.state-3 {
top: 120px;
animation: anim-cube-3-1 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1)
infinite;
}
&.state-3-3 {
top: 120px;
transform: translateZ(-120px);
animation: anim-cube-3-2 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1)
infinite;
}
&.state-4 {
top: 120px;
left: 120px;
animation: anim-cube-4-1 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1)
infinite;
}
&.state-4-4 {
top: 120px;
left: 120px;
transform: translateZ(-120px);
animation: anim-cube-4-2 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1)
infinite;
}
}
}
&.bottom {
transform: rotateX(-90deg) translateZ(50px);
}
}
@keyframes anim-cube-1-1 {
.cube1Front {
animation: cube1Front 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
.cube1Rear {
transform: translateZ(-120px);
animation: cube1Rear 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
.cube2Front {
left: 120px;
animation: cube2Front 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
.cube2Rear {
left: 120px;
transform: translateZ(-120px);
animation: cube2Rear 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
.cube3Front {
top: 120px;
animation: cube3Front 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
.cube3Rear {
top: 120px;
transform: translateZ(-120px);
animation: cube3Rear 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
.cube4Front {
top: 120px;
left: 120px;
animation: cube4Front 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
.cube4Rear {
top: 120px;
left: 120px;
transform: translateZ(-120px);
animation: anim-cube-4-2 8s 0.32s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
@keyframes cube1Front {
/* STEP 1 */
0% {
left: 0px;
@@ -191,7 +173,68 @@ div.creating {
}
}
@keyframes anim-cube-2-1 {
@keyframes cube1Rear {
/* STEP 1 */
0% {
left: 0px;
transform: translateZ(-120px);
}
2.083% {
left: -40px;
transform: translateZ(-120px);
}
16.666% {
left: -40px;
transform: translateZ(-120px);
}
/* STEP 2 */
18.749% {
left: 0px;
transform: translateZ(-120px);
}
33.332% {
left: 0px;
transform: translateZ(-120px);
}
/* STEP 3 */
35.415% {
left: 0px;
transform: translateZ(-200px);
}
49.998% {
left: 0px;
transform: translateZ(-200px);
}
/* STEP 4 */
52.081% {
left: 0px;
transform: translateZ(-120px);
}
66.664% {
left: 0px;
transform: translateZ(-120px);
}
/* Step 5 */
68.747% {
left: -60px;
transform: translateZ(-180px);
}
83.33% {
left: -60px;
transform: translateZ(-180px);
}
/* Step 6 */
85.413% {
left: 0px;
transform: translateZ(-120px);
}
100% {
left: 0px;
transform: translateZ(-120px);
}
}
@keyframes cube2Front {
/* STEP 1 */
0% {
left: 120px;
@@ -261,7 +304,7 @@ div.creating {
}
}
@keyframes anim-cube-3-1 {
@keyframes cube3Front {
/* STEP 1 */
0% {
top: 120px;
@@ -331,7 +374,7 @@ div.creating {
}
}
@keyframes anim-cube-4-1 {
@keyframes cube4Front {
/* STEP 1 */
0% {
top: 120px;
@@ -405,68 +448,7 @@ div.creating {
}
}
@keyframes anim-cube-1-2 {
/* STEP 1 */
0% {
left: 0px;
transform: translateZ(-120px);
}
2.083% {
left: -40px;
transform: translateZ(-120px);
}
16.666% {
left: -40px;
transform: translateZ(-120px);
}
/* STEP 2 */
18.749% {
left: 0px;
transform: translateZ(-120px);
}
33.332% {
left: 0px;
transform: translateZ(-120px);
}
/* STEP 3 */
35.415% {
left: 0px;
transform: translateZ(-200px);
}
49.998% {
left: 0px;
transform: translateZ(-200px);
}
/* STEP 4 */
52.081% {
left: 0px;
transform: translateZ(-120px);
}
66.664% {
left: 0px;
transform: translateZ(-120px);
}
/* Step 5 */
68.747% {
left: -60px;
transform: translateZ(-180px);
}
83.33% {
left: -60px;
transform: translateZ(-180px);
}
/* Step 6 */
85.413% {
left: 0px;
transform: translateZ(-120px);
}
100% {
left: 0px;
transform: translateZ(-120px);
}
}
@keyframes anim-cube-2-2 {
@keyframes cube2Rear {
/* STEP 1 */
0% {
left: 120px;
@@ -527,7 +509,7 @@ div.creating {
}
}
@keyframes anim-cube-3-2 {
@keyframes cube3Rear {
/* STEP 1 */
0% {
top: 120px;

View File

@@ -0,0 +1,19 @@
import { CubeConstruction } from "./CubeConstruction";
import { Meta, StoryObj } from "@kachurun/storybook-solid";
const meta: Meta = {
title: "Components/CubeConstruction",
component: CubeConstruction,
globals: {
// 👇 Override background value for this story
backgrounds: { value: "dark" },
},
};
export default meta;
type Story = StoryObj;
export const Default: Story = {
args: {},
};

View File

@@ -0,0 +1,79 @@
import styles from "./CubeConstruction.module.css";
import cx from "classnames";
export const CubeConstruction = () => (
<div class={styles.cubeConstruction}>
<div class={styles.scene}>
<div class={cx(styles.cube, styles.cube1Front)}>
<div class={cx(styles.face, styles.front)}></div>
<div class={cx(styles.face, styles.left)}></div>
<div class={cx(styles.face, styles.right)}></div>
<div class={cx(styles.face, styles.top)}></div>
<div class={cx(styles.face, styles.bottom)}></div>
<div class={cx(styles.face, styles.back)}></div>
</div>
<div class={cx(styles.cube, styles.cube1Rear)}>
<div class={cx(styles.face, styles.front)}></div>
<div class={cx(styles.face, styles.left)}></div>
<div class={cx(styles.face, styles.right)}></div>
<div class={cx(styles.face, styles.top)}></div>
<div class={cx(styles.face, styles.bottom)}></div>
<div class={cx(styles.face, styles.back)}></div>
</div>
<div class={cx(styles.cube, styles.cube2Front)}>
<div class={cx(styles.face, styles.front)}></div>
<div class={cx(styles.face, styles.left)}></div>
<div class={cx(styles.face, styles.right)}></div>
<div class={cx(styles.face, styles.top)}></div>
<div class={cx(styles.face, styles.bottom)}></div>
<div class={cx(styles.face, styles.back)}></div>
</div>
<div class={cx(styles.cube, styles.cube2Rear)}>
<div class={cx(styles.face, styles.front)}></div>
<div class={cx(styles.face, styles.left)}></div>
<div class={cx(styles.face, styles.right)}></div>
<div class={cx(styles.face, styles.top)}></div>
<div class={cx(styles.face, styles.bottom)}></div>
<div class={cx(styles.face, styles.back)}></div>
</div>
<div class={cx(styles.cube, styles.cube3Front)}>
<div class={cx(styles.face, styles.front)}></div>
<div class={cx(styles.face, styles.left)}></div>
<div class={cx(styles.face, styles.right)}></div>
<div class={cx(styles.face, styles.top)}></div>
<div class={cx(styles.face, styles.bottom)}></div>
<div class={cx(styles.face, styles.back)}></div>
</div>
<div class={cx(styles.cube, styles.cube3Rear)}>
<div class={cx(styles.face, styles.front)}></div>
<div class={cx(styles.face, styles.left)}></div>
<div class={cx(styles.face, styles.right)}></div>
<div class={cx(styles.face, styles.top)}></div>
<div class={cx(styles.face, styles.bottom)}></div>
<div class={cx(styles.face, styles.back)}></div>
</div>
<div class={cx(styles.cube, styles.cube4Front)}>
<div class={cx(styles.face, styles.front)}></div>
<div class={cx(styles.face, styles.left)}></div>
<div class={cx(styles.face, styles.right)}></div>
<div class={cx(styles.face, styles.top)}></div>
<div class={cx(styles.face, styles.bottom)}></div>
<div class={cx(styles.face, styles.back)}></div>
</div>
<div class={cx(styles.cube, styles.cube4Rear)}>
<div class={cx(styles.face, styles.front)}></div>
<div class={cx(styles.face, styles.left)}></div>
<div class={cx(styles.face, styles.right)}></div>
<div class={cx(styles.face, styles.top)}></div>
<div class={cx(styles.face, styles.bottom)}></div>
<div class={cx(styles.face, styles.back)}></div>
</div>
</div>
</div>
);

View File

@@ -1,91 +0,0 @@
import { Tooltip } from "@/src/components/Tooltip/Tooltip";
import "./Creating.css";
export const Creating = () => (
<div class="creating">
<Tooltip
open={true}
placement="top"
description={"Your Clan is being created"}
>
<div></div>
</Tooltip>
<div class="scene">
<div class="frame">
<div id="cube-1" class="cube state-1">
<div class="cube-face front"></div>
<div class="cube-face left"></div>
<div class="cube-face right"></div>
<div class="cube-face top"></div>
<div class="cube-face bottom"></div>
<div class="cube-face back"></div>
</div>
<div id="cube-2" class="cube state-2">
<div class="cube-face front"></div>
<div class="cube-face left"></div>
<div class="cube-face right"></div>
<div class="cube-face top"></div>
<div class="cube-face bottom"></div>
<div class="cube-face back"></div>
</div>
<div id="cube-3" class="cube state-3">
<div class="cube-face front"></div>
<div class="cube-face left"></div>
<div class="cube-face right"></div>
<div class="cube-face top"></div>
<div class="cube-face bottom"></div>
<div class="cube-face back"></div>
</div>
<div id="cube-4" class="cube state-4">
<div class="cube-face front"></div>
<div class="cube-face left"></div>
<div class="cube-face right"></div>
<div class="cube-face top"></div>
<div class="cube-face bottom"></div>
<div class="cube-face back"></div>
</div>
<div id="cube-1-1" class="cube state-1-1">
<div class="cube-face front"></div>
<div class="cube-face left"></div>
<div class="cube-face right"></div>
<div class="cube-face top"></div>
<div class="cube-face bottom"></div>
<div class="cube-face back"></div>
</div>
<div id="cube-2-2" class="cube state-2-2">
<div class="cube-face front"></div>
<div class="cube-face left"></div>
<div class="cube-face right"></div>
<div class="cube-face top"></div>
<div class="cube-face bottom"></div>
<div class="cube-face back"></div>
</div>
<div id="cube-3-3" class="cube state-3-3">
<div class="cube-face front"></div>
<div class="cube-face left"></div>
<div class="cube-face right"></div>
<div class="cube-face top"></div>
<div class="cube-face bottom"></div>
<div class="cube-face back"></div>
</div>
<div id="cube-4-4" class="cube state-4-4">
<div class="cube-face front"></div>
<div class="cube-face left"></div>
<div class="cube-face right"></div>
<div class="cube-face top"></div>
<div class="cube-face bottom"></div>
<div class="cube-face back"></div>
</div>
</div>
</div>
</div>
);

View File

@@ -65,3 +65,10 @@
.setupFormControls {
@apply flex justify-end pt-6;
}
.creating {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

View File

@@ -34,9 +34,10 @@ import { TextArea } from "@/src/components/Form/TextArea";
import { Fieldset } from "@/src/components/Form/Fieldset";
import * as v from "valibot";
import { HostFileInput } from "@/src/components/Form/HostFileInput";
import { Creating } from "./Creating";
import { useApiClient } from "@/src/hooks/ApiClient";
import { ListClansModal } from "@/src/modals/ListClansModal/ListClansModal";
import { Tooltip } from "@/src/components/Tooltip/Tooltip";
import { CubeConstruction } from "@/src/components/CubeConstruction/CubeConstruction";
type State = "welcome" | "setup" | "creating";
@@ -349,7 +350,16 @@ export const Onboarding: Component<RouteSectionProps> = (props) => {
</Match>
<Match when={state() === "creating"}>
<Creating />
<div class={styles.creating}>
<Tooltip
open={true}
placement="top"
description={"Your Clan is being created"}
>
<div></div>
</Tooltip>
<CubeConstruction />
</div>
</Match>
</Switch>
</div>