diff --git a/.dockerignore b/.dockerignore index 076405933..b3204ffac 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,27 +1,2 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js -*.swp - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.vscode -.idea -.env -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* +**/node_modules +**/build diff --git a/.eslintrc.json b/.eslintrc.json index 867e5a0fb..d6cc4fc8e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -10,7 +10,8 @@ "plugin:cypress/recommended", "eslint:recommended", "plugin:react/recommended", - "prettier" + "prettier", + "plugin:storybook/recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { @@ -24,7 +25,12 @@ "rules": { "no-unused-vars": 1, "react/react-in-jsx-scope": "off", - "react/no-unknown-property": ["error", { "ignore": ["css"] }] + "react/no-unknown-property": [ + "error", + { + "ignore": ["css"] + } + ] }, "overrides": [ { diff --git a/.gitignore b/.gitignore index ce76aad3d..502f6fbed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies -/node_modules +**/node_modules /.pnp .pnp.js package-lock.json @@ -11,7 +11,7 @@ package-lock.json /coverage # production -/build +**/build # misc .DS_Store diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..d67f37488 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +node-linker=hoisted diff --git a/.prettierrc.json b/.prettierrc.json index 018d593fd..b8df65427 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -15,13 +15,13 @@ "plugins": ["@trivago/prettier-plugin-sort-imports"], "importOrder": [ "", - "^types/", - "^hooks/", - "^components/|^pages/", + "^@/types/", + "^@/hooks/", + "^@/components/|^@/pages/", "^[./]", - "^atoms/|^recoil$", - "^tools/", - "^static/|^loadenv$|^@mui|^@material-ui" + "^@/atoms/|^recoil$", + "^@/tools/", + "^@/static/|^@mui|^@material-ui" ], "importOrderSeparation": true, "importOrderSortSpecifiers": true, diff --git a/Dockerfile b/Dockerfile index 402bf5c5b..783d8f6fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,17 +12,23 @@ RUN apt-get -qq update; \ nvm use --delete-prefix default ENV PATH /root/.nvm/versions/node/$NODE_VERSION/bin:$PATH +# Install base dependencies +RUN npm install --global pnpm@8.6.6 serve@14.1.2 react-inject-env@2.1.0 + +# Copy lockfile and prefetch depdendencies +COPY pnpm-lock.yaml . +RUN pnpm fetch + # Copy repository COPY . . -# Install requirements -RUN npm install --global pnpm@8.6.6 serve@14.1.2; \ - pnpm install; \ - pnpm install react-inject-env@2.1.0 --save - # Build -RUN pnpm run build; \ - chmod 711 /root +RUN pnpm --filter @taxi/web... install --offline; \ + pnpm --filter @taxi/web... build + +# Move built files to root +RUN mv /root/packages/web/build . +RUN chmod 711 /root # Set default environment variables ENV REACT_APP_BACK_URL=https://taxi.sparcs.org/api \ diff --git a/README.md b/README.md index cca0ffc85..43277bda2 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,32 @@ $ pnpm install See [notion page](https://www.notion.so/sparcs/Environment-Variables-1b404bd385fa495bac6d5517b57d72bf). Refer to [.env.example](.env.example) and write your own `.env`. + +## Development + +Run scoped scripts + +```bash +pnpm - + +
diff --git a/packages/web/package.json b/packages/web/package.json new file mode 100644 index 000000000..4fb7245bd --- /dev/null +++ b/packages/web/package.json @@ -0,0 +1,91 @@ +{ + "name": "@taxi/web", + "version": "0.1.0", + "private": true, + "dependencies": { + "@emotion/css": "^11.10.6", + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6", + "@mui/icons-material": "^5.14.1", + "@mui/material": "^5.4.1", + "antd": "^5.4.6", + "axios": "^0.21.1", + "browser-image-compression": "^2.0.0", + "dayjs": "^1.11.7", + "firebase": "^9.15.0", + "heic2any": "^0.0.4", + "i18next": "^22.0.2", + "i18next-browser-languagedetector": "^7.0.1", + "moment": "^2.29.3", + "prop-types": "^15.8.1", + "qs": "^6.11.0", + "react": "^18.2.0", + "react-cookie": "^4.1.1", + "react-dom": "^18.2.0", + "react-ga4": "^1.4.1", + "react-i18next": "^12.0.0", + "react-qr-code": "^2.0.11", + "react-router-dom": "^5.3.3", + "react-toastify": "8.2.0", + "recoil": "^0.7.5", + "socket.io-client": "^4.3.2", + "use-state-with-callback": "^3.0.2" + }, + "scripts": { + "start": "vite", + "build": "tsc && vite build", + "typecheck": "tsc --noEmit", + "preinstall": "npx only-allow pnpm", + "test": "cypress open", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "@storybook/addon-essentials": "^7.5.2", + "@storybook/addon-interactions": "^7.5.2", + "@storybook/addon-links": "^7.5.2", + "@storybook/addon-onboarding": "^1.0.8", + "@storybook/blocks": "^7.5.2", + "@storybook/react": "^7.5.2", + "@storybook/react-vite": "^7.5.2", + "@storybook/testing-library": "^0.2.2", + "@trivago/prettier-plugin-sort-imports": "^4.1.1", + "@types/eslint": "^8.44.0", + "@types/eslint-config-prettier": "^6.11.0", + "@types/prop-types": "^15.7.5", + "@types/qs": "^6.9.7", + "@types/react": "^18.2.15", + "@types/react-dom": "^18.2.7", + "@types/react-router-dom": "^5.3.3", + "@typescript-eslint/eslint-plugin": "^5.37.0", + "@typescript-eslint/parser": "^5.37.0", + "@vitejs/plugin-react-swc": "^3.4.1", + "cypress": "^10.3.1", + "cypress-react-selector": "^3.0.0", + "eslint": "^7.32.0", + "eslint-config-airbnb": "^18.2.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-cypress": "^2.12.1", + "eslint-plugin-import": "^2.24.2", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-react": "^7.26.1", + "eslint-plugin-react-hooks": "^4.2.0", + "eslint-plugin-storybook": "^0.6.15", + "storybook": "^7.5.2", + "typescript": "^5.1.6", + "vite": "^4.5.0", + "vite-plugin-svgr": "^3.2.0" + } +} diff --git a/packages/web/public/2024springEvent-graph.png b/packages/web/public/2024springEvent-graph.png new file mode 100644 index 000000000..6541f9dcf Binary files /dev/null and b/packages/web/public/2024springEvent-graph.png differ diff --git a/public/apple-touch-icon.png b/packages/web/public/apple-touch-icon.png similarity index 100% rename from public/apple-touch-icon.png rename to packages/web/public/apple-touch-icon.png diff --git a/public/favicon.ico b/packages/web/public/favicon.ico similarity index 100% rename from public/favicon.ico rename to packages/web/public/favicon.ico diff --git a/public/firebase-messaging-sw.js b/packages/web/public/firebase-messaging-sw.js similarity index 100% rename from public/firebase-messaging-sw.js rename to packages/web/public/firebase-messaging-sw.js diff --git a/public/graph.png b/packages/web/public/graph.png similarity index 100% rename from public/graph.png rename to packages/web/public/graph.png diff --git a/public/icon.svg b/packages/web/public/icon.svg similarity index 100% rename from public/icon.svg rename to packages/web/public/icon.svg diff --git a/public/icons-192.png b/packages/web/public/icons-192.png similarity index 100% rename from public/icons-192.png rename to packages/web/public/icons-192.png diff --git a/public/icons-512.png b/packages/web/public/icons-512.png similarity index 100% rename from public/icons-512.png rename to packages/web/public/icons-512.png diff --git a/public/manifest.json b/packages/web/public/manifest.json similarity index 100% rename from public/manifest.json rename to packages/web/public/manifest.json diff --git a/public/robots.txt b/packages/web/public/robots.txt similarity index 100% rename from public/robots.txt rename to packages/web/public/robots.txt diff --git a/src/Font.css b/packages/web/src/Font.css similarity index 67% rename from src/Font.css rename to packages/web/src/Font.css index 8cea15790..ed30ca512 100644 --- a/src/Font.css +++ b/packages/web/src/Font.css @@ -25,3 +25,17 @@ url("./static/fonts/NanumSquare_acEB.ttf") format("truetype"); font-weight: 700; } + +@font-face { + font-family: "Galmuri11"; + src: local("Galmuri11"), + url("./static/fonts/Galmuri11.ttf") format("truetype"); + font-weight: 400; +} + +@font-face { + font-family: "Galmuri11"; + src: local("Galmuri11"), + url("./static/fonts/Galmuri11-Bold.ttf") format("truetype"); + font-weight: 700; +} diff --git a/src/atoms/alert.ts b/packages/web/src/atoms/alert.ts similarity index 100% rename from src/atoms/alert.ts rename to packages/web/src/atoms/alert.ts diff --git a/src/atoms/error.ts b/packages/web/src/atoms/error.ts similarity index 100% rename from src/atoms/error.ts rename to packages/web/src/atoms/error.ts diff --git a/packages/web/src/atoms/event2023FallInfo.ts b/packages/web/src/atoms/event2023FallInfo.ts new file mode 100644 index 000000000..4b6e202c9 --- /dev/null +++ b/packages/web/src/atoms/event2023FallInfo.ts @@ -0,0 +1,19 @@ +import { Quest, QuestId } from "@/types/event2023fall"; + +import { atom } from "recoil"; + +export type Event2023FallInfoType = Nullable<{ + isAgreeOnTermsOfEvent: boolean; + completedQuests: QuestId[]; + creditAmount: number; + ticket1Amount: number; + ticket2Amount: number; + quests: Quest[]; +}>; + +const event2023FallInfoAtom = atom({ + key: "event2023FallInfoAtom", + default: null, +}); + +export default event2023FallInfoAtom; diff --git a/packages/web/src/atoms/event2024SpringInfo.ts b/packages/web/src/atoms/event2024SpringInfo.ts new file mode 100644 index 000000000..5c263fdbc --- /dev/null +++ b/packages/web/src/atoms/event2024SpringInfo.ts @@ -0,0 +1,22 @@ +import { Quest, QuestId } from "@/types/event2024spring"; + +import { atom } from "recoil"; + +export type Event2024SpringInfoType = Nullable<{ + isAgreeOnTermsOfEvent: boolean; + isEligible: boolean; + completedQuests: QuestId[]; + creditAmount: number; + groupCreditAmount: number; + group: number; + ticket1Amount: number; + ticket2Amount: number; + quests: Quest[]; +}>; + +const event2024SpringInfoAtom = atom({ + key: "event2024SpringInfoAtom", + default: null, +}); + +export default event2024SpringInfoAtom; diff --git a/src/atoms/isVirtualKeyboardDetected.ts b/packages/web/src/atoms/isVirtualKeyboardDetected.ts similarity index 100% rename from src/atoms/isVirtualKeyboardDetected.ts rename to packages/web/src/atoms/isVirtualKeyboardDetected.ts diff --git a/src/atoms/loginInfo.ts b/packages/web/src/atoms/loginInfo.ts similarity index 83% rename from src/atoms/loginInfo.ts rename to packages/web/src/atoms/loginInfo.ts index a199bf478..e25c8ad48 100644 --- a/src/atoms/loginInfo.ts +++ b/packages/web/src/atoms/loginInfo.ts @@ -12,8 +12,9 @@ export type LoginInfoType = Nullable<{ profileImgUrl: string; subinfo: { kaist: string; sparcs: string; facebook: string; twitter: string }; withdraw: boolean; + phoneNumber?: string; account: string; - deviceType: "web" | "app"; + // deviceType: "web" | "app"; // #580 - loadenv의 deviceType을 사용하여야 합니다. deviceToken: Nullable; accessToken: Nullable; refreshToken: Nullable; diff --git a/src/atoms/modals.ts b/packages/web/src/atoms/modals.ts similarity index 77% rename from src/atoms/modals.ts rename to packages/web/src/atoms/modals.ts index 46a314fd6..2150a112b 100644 --- a/src/atoms/modals.ts +++ b/packages/web/src/atoms/modals.ts @@ -1,4 +1,4 @@ -import { ModalElemProps } from "components/Modal/ModalElem"; +import { ModalElemProps } from "@/components/Modal/ModalElem"; import { atom } from "recoil"; diff --git a/src/atoms/myRooms.ts b/packages/web/src/atoms/myRooms.ts similarity index 100% rename from src/atoms/myRooms.ts rename to packages/web/src/atoms/myRooms.ts diff --git a/src/atoms/notificationOptions.ts b/packages/web/src/atoms/notificationOptions.ts similarity index 100% rename from src/atoms/notificationOptions.ts rename to packages/web/src/atoms/notificationOptions.ts diff --git a/src/atoms/taxiLocations.ts b/packages/web/src/atoms/taxiLocations.ts similarity index 81% rename from src/atoms/taxiLocations.ts rename to packages/web/src/atoms/taxiLocations.ts index fe7b9a7bc..96cab1e07 100644 --- a/src/atoms/taxiLocations.ts +++ b/packages/web/src/atoms/taxiLocations.ts @@ -1,4 +1,4 @@ -import type { Location } from "types/location"; +import type { Location } from "@/types/location"; import { atom } from "recoil"; diff --git a/src/components/AdaptiveDiv/AdaptiveButterfly.tsx b/packages/web/src/components/AdaptiveDiv/AdaptiveButterfly.tsx similarity index 90% rename from src/components/AdaptiveDiv/AdaptiveButterfly.tsx rename to packages/web/src/components/AdaptiveDiv/AdaptiveButterfly.tsx index cfbfb4646..6f31dbc23 100644 --- a/src/components/AdaptiveDiv/AdaptiveButterfly.tsx +++ b/packages/web/src/components/AdaptiveDiv/AdaptiveButterfly.tsx @@ -1,10 +1,10 @@ import { ReactNode } from "react"; -import useButterflyState from "hooks/useButterflyState"; +import useButterflyState from "@/hooks/useButterflyState"; import AdaptiveCenter from "./AdaptiveCenter"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; export type AdaptiveButterflyProps = { left?: ReactNode; diff --git a/src/components/AdaptiveDiv/AdaptiveCenter.tsx b/packages/web/src/components/AdaptiveDiv/AdaptiveCenter.tsx similarity index 51% rename from src/components/AdaptiveDiv/AdaptiveCenter.tsx rename to packages/web/src/components/AdaptiveDiv/AdaptiveCenter.tsx index a762c4d5d..71b865f81 100644 --- a/src/components/AdaptiveDiv/AdaptiveCenter.tsx +++ b/packages/web/src/components/AdaptiveDiv/AdaptiveCenter.tsx @@ -1,13 +1,19 @@ -import { ReactNode } from "react"; +import { HTMLProps, ReactNode } from "react"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; export type AdaptiveCenterProps = { - children: ReactNode; -}; + children?: ReactNode; + className?: string; +} & HTMLProps; -const AdaptiveCenter = ({ children }: AdaptiveCenterProps) => ( +const AdaptiveCenter = ({ + children, + className, + ...divProps +}: AdaptiveCenterProps) => (
( }px, 100%) - ${theme.adaptivediv.margin * 2}px)`, margin: "auto", }} + {...divProps} > {children}
diff --git a/src/components/AdaptiveDiv/AdaptiveModal.tsx b/packages/web/src/components/AdaptiveDiv/AdaptiveModal.tsx similarity index 93% rename from src/components/AdaptiveDiv/AdaptiveModal.tsx rename to packages/web/src/components/AdaptiveDiv/AdaptiveModal.tsx index 5f9ed6d94..12b4a41a0 100644 --- a/src/components/AdaptiveDiv/AdaptiveModal.tsx +++ b/packages/web/src/components/AdaptiveDiv/AdaptiveModal.tsx @@ -1,6 +1,6 @@ import { ReactNode } from "react"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; export type AdaptiveModalProps = { width?: PixelValue; // CSS["width"]; diff --git a/src/components/AdaptiveDiv/index.tsx b/packages/web/src/components/AdaptiveDiv/index.tsx similarity index 86% rename from src/components/AdaptiveDiv/index.tsx rename to packages/web/src/components/AdaptiveDiv/index.tsx index fa13fd922..ae1a115ae 100644 --- a/src/components/AdaptiveDiv/index.tsx +++ b/packages/web/src/components/AdaptiveDiv/index.tsx @@ -2,10 +2,11 @@ import AdaptiveButterfly, { AdaptiveButterflyProps } from "./AdaptiveButterfly"; import AdaptiveCenter, { AdaptiveCenterProps } from "./AdaptiveCenter"; import AdaptiveModal, { AdaptiveModalProps } from "./AdaptiveModal"; -type AdaptiveDivProps = +type AdaptiveDivProps = { className?: string } & ( | (AdaptiveButterflyProps & { type: "butterfly" }) | (AdaptiveCenterProps & { type: "center" }) - | (AdaptiveModalProps & { type: "modal" }); + | (AdaptiveModalProps & { type: "modal" }) +); const AdaptiveDiv = (props: AdaptiveDivProps) => { switch (props.type) { diff --git a/src/components/Button/ButtonShare.tsx b/packages/web/src/components/Button/ButtonShare.tsx similarity index 96% rename from src/components/Button/ButtonShare.tsx rename to packages/web/src/components/Button/ButtonShare.tsx index b6a0690e0..de03bace2 100644 --- a/src/components/Button/ButtonShare.tsx +++ b/packages/web/src/components/Button/ButtonShare.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; type ButtonShareProps = { text: string; diff --git a/packages/web/src/components/Button/index.stories.tsx b/packages/web/src/components/Button/index.stories.tsx new file mode 100644 index 000000000..720fd995f --- /dev/null +++ b/packages/web/src/components/Button/index.stories.tsx @@ -0,0 +1,53 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import Button from "./index"; + +import theme from "@/tools/theme"; + +const meta: Meta = { + component: Button, + parameters: { + layout: "centered", + }, + argTypes: { + children: { + control: { + type: "text", + }, + }, + type: { + control: { + type: "select", + options: ["purple", "purple_inset", "gray", "white"], + }, + }, + disabled: { + control: { + type: "boolean", + }, + }, + }, +}; + +const styleButton = { + padding: "14px", + borderRadius: "12px", + ...theme.font14_bold, +}; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + args: { + type: "purple", + disabled: false, + children: "Button", + }, + render: (args) => ( + + ), +}; diff --git a/src/components/Button/index.tsx b/packages/web/src/components/Button/index.tsx similarity index 81% rename from src/components/Button/index.tsx rename to packages/web/src/components/Button/index.tsx index ae27859dc..8ecad944b 100644 --- a/src/components/Button/index.tsx +++ b/packages/web/src/components/Button/index.tsx @@ -1,33 +1,25 @@ -import { ReactNode } from "react"; +import { HTMLProps, ReactNode } from "react"; -import useHoverProps from "hooks/theme/useHoverProps"; +import useHoverProps from "@/hooks/theme/useHoverProps"; -import theme, { Font } from "tools/theme"; +import theme from "@/tools/theme"; type ButtonType = "purple" | "purple_inset" | "gray" | "white"; type ButtonProps = { type?: ButtonType; disabled?: boolean; - width?: string; - padding?: string | number; - radius?: number; - font?: Font; - onClick?: () => void; className?: string; children?: ReactNode; -}; +} & HTMLProps; const Button = ({ type, disabled = false, - width, - padding, - radius, - font, - onClick, className, children, + onClick, + ...divProps }: ButtonProps) => { const [hoverProps, isHover, isClicked] = useHoverProps(); @@ -71,23 +63,20 @@ const Button = ({ } }; - const style: CSS = { - ...font, - width: width, - padding: padding, - borderRadius: radius, + const style = { transitionDuration: theme.duration, + textAlign: "center" as const, ...theme.cursor(disabled), - textAlign: "center", ...getColor(), }; return (
{children}
diff --git a/src/components/Chat/Container/index.tsx b/packages/web/src/components/Chat/Container/index.tsx similarity index 80% rename from src/components/Chat/Container/index.tsx rename to packages/web/src/components/Chat/Container/index.tsx index 7c364eb85..26846e8ac 100644 --- a/src/components/Chat/Container/index.tsx +++ b/packages/web/src/components/Chat/Container/index.tsx @@ -1,6 +1,6 @@ -import HeaderBar from "components/HeaderBar"; +import HeaderBar from "@/components/Header/HeaderBar"; -import isMobile from "tools/isMobile"; +import { deviceType } from "@/tools/loadenv"; type ContainerProps = { layoutType: "sidechat" | "fullchat"; @@ -8,11 +8,11 @@ type ContainerProps = { }; const Container = ({ layoutType, children }: ContainerProps) => { - const [, isIOS] = isMobile(); // const isVKDetected = useRecoilValue(isVirtualKeyboardDetectedAtom); // IOS #254 이슈 대응 크로스 브라우징 - const isIOSCrossBrowsing = isIOS && layoutType === "fullchat"; // && isVKDetected; + const isIOSCrossBrowsing = + deviceType.endsWith("/ios") && layoutType === "fullchat"; // && isVKDetected; // isVKDetected 옵션 사용시 리랜더링 됨 return ( diff --git a/src/components/Chat/Header/SideMenu.tsx b/packages/web/src/components/Chat/Header/SideMenu.tsx similarity index 95% rename from src/components/Chat/Header/SideMenu.tsx rename to packages/web/src/components/Chat/Header/SideMenu.tsx index 6ad9f54c5..f9808ef39 100644 --- a/src/components/Chat/Header/SideMenu.tsx +++ b/packages/web/src/components/Chat/Header/SideMenu.tsx @@ -1,22 +1,21 @@ -import dayjs from "dayjs"; import { memo, useCallback, useState } from "react"; -import useIsTimeOver from "hooks/useIsTimeOver"; +import useIsTimeOver from "@/hooks/useIsTimeOver"; -import DottedLine from "components/DottedLine"; +import DottedLine from "@/components/DottedLine"; import { ModalCallTaxi, ModalChatCancel, ModalChatReport, ModalRoomShare, -} from "components/ModalPopup"; -import User from "components/User"; +} from "@/components/ModalPopup"; +import User from "@/components/User"; -import alertAtom from "atoms/alert"; +import alertAtom from "@/atoms/alert"; import { useSetRecoilState } from "recoil"; -import { day2str, dayServerToClient } from "tools/day"; -import theme from "tools/theme"; +import dayjs, { day2str, dayServerToClient } from "@/tools/day"; +import theme from "@/tools/theme"; import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded"; import CalendarTodayRoundedIcon from "@mui/icons-material/CalendarTodayRounded"; diff --git a/src/components/Chat/Header/index.tsx b/packages/web/src/components/Chat/Header/index.tsx similarity index 95% rename from src/components/Chat/Header/index.tsx rename to packages/web/src/components/Chat/Header/index.tsx index 733a6dc6d..03895be04 100644 --- a/src/components/Chat/Header/index.tsx +++ b/packages/web/src/components/Chat/Header/index.tsx @@ -1,13 +1,13 @@ import { useState } from "react"; import { Link, useHistory } from "react-router-dom"; -import type { LayoutType } from "types/chat"; +import type { LayoutType } from "@/types/chat"; -import useButterflyState from "hooks/useButterflyState"; +import useButterflyState from "@/hooks/useButterflyState"; import SideMenu from "./SideMenu"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; import CloseFullscreenRoundedIcon from "@mui/icons-material/CloseFullscreenRounded"; diff --git a/src/components/Chat/MessageForm/InputText/BodyImage.tsx b/packages/web/src/components/Chat/MessageForm/InputText/BodyImage.tsx similarity index 95% rename from src/components/Chat/MessageForm/InputText/BodyImage.tsx rename to packages/web/src/components/Chat/MessageForm/InputText/BodyImage.tsx index 745e83cac..39fd0fbb9 100644 --- a/src/components/Chat/MessageForm/InputText/BodyImage.tsx +++ b/packages/web/src/components/Chat/MessageForm/InputText/BodyImage.tsx @@ -1,14 +1,14 @@ import { useCallback, useEffect, useState } from "react"; -import useSendMessage from "hooks/chat/useSendMessage"; +import useSendMessage from "@/hooks/chat/useSendMessage"; import ButtonSend from "./ButtonSend"; -import alertAtom from "atoms/alert"; +import alertAtom from "@/atoms/alert"; import { useSetRecoilState } from "recoil"; -import { convertImage, getImageSrc } from "tools/image"; -import theme from "tools/theme"; +import { convertImage, getImageSrc } from "@/tools/image"; +import theme from "@/tools/theme"; import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; import MotionPhotosOnIcon from "@mui/icons-material/RotateLeftRounded"; diff --git a/packages/web/src/components/Chat/MessageForm/InputText/BodyText.tsx b/packages/web/src/components/Chat/MessageForm/InputText/BodyText.tsx new file mode 100644 index 000000000..2b4aea423 --- /dev/null +++ b/packages/web/src/components/Chat/MessageForm/InputText/BodyText.tsx @@ -0,0 +1,160 @@ +import { css } from "@emotion/react"; +import { useCallback, useEffect, useRef, useState } from "react"; + +import useSendMessage from "@/hooks/chat/useSendMessage"; + +import ButtonSend from "./ButtonSend"; + +import regExpTest from "@/tools/regExpTest"; +import theme from "@/tools/theme"; + +type BodyTextProps = { + sendMessage: ReturnType; +}; + +const BodyText = ({ sendMessage }: BodyTextProps) => { + const wrapRef = useRef(null); + const textareaRef = useRef(); + const [height, setHeight] = useState("32px"); + const [isSendingMessage, setIsSendingMessage] = useState(false); + const isEnterPressed = useRef(false); + const isShiftPressed = useRef(false); + + /* form height handler */ + const resizeEvent = useCallback(() => { + if (!wrapRef.current) return; + const cacheHeight = wrapRef.current.style.height; + wrapRef.current.style.height = "0"; + const newHeight = `${Math.max( + Math.min( + textareaRef.current ? textareaRef.current.scrollHeight : 0, + document.body.clientHeight / 3 + ), + 32 + )}px`; + wrapRef.current.style.height = cacheHeight; + setHeight(newHeight); + }, []); + useEffect(() => { + resizeEvent(); + window.addEventListener("resize", resizeEvent); + return () => window.removeEventListener("resize", resizeEvent); + }, []); + + /* message validation handler */ + const [isMessageValidState, setIsMessageValidState] = + useState(false); + const getIsMessageValid = useCallback( + (message: string): boolean => + regExpTest.chatMsg(message) && !isSendingMessage, + [isSendingMessage] + ); + useEffect( + () => + setIsMessageValidState( + getIsMessageValid(textareaRef.current?.value || "") + ), + [getIsMessageValid] + ); + + /* send message handler */ + const onSend = async () => { + if (!textareaRef.current) return; + + const message = textareaRef.current.value; + const isMessageValid = getIsMessageValid(message); + refreshTextArea(); + textareaRef.current.focus(); + resizeEvent(); + + if (isMessageValid) { + setIsSendingMessage(true); + const result = await sendMessage("text", { text: message }); + if (!result) textareaRef.current.value = message; + setIsSendingMessage(false); + } + }; + + /* textarea event handler */ + const onChange = useCallback( + (e: Event) => { + if (!textareaRef.current) return; + if (isSendingMessage) refreshTextArea(); + setIsMessageValidState(getIsMessageValid(textareaRef.current.value)); + + if (isEnterPressed.current && !isShiftPressed.current) { + onSend(); + return; + } + resizeEvent(); + }, + [isSendingMessage, getIsMessageValid, onSend] + ); + const onKeyEvent = (e: KeyboardEvent, v: boolean) => { + if (e.code === "ShiftLeft" || e.code === "ShiftRight") + isShiftPressed.current = v; + if (e.code === "Enter") { + if (textareaRef.current && !v && !isShiftPressed.current) { + refreshTextArea(); + textareaRef.current.focus(); + } + isEnterPressed.current = v; + } + }; + const onKeyDown = (e: KeyboardEvent) => onKeyEvent(e, true); + const onKeyUp = (e: KeyboardEvent) => onKeyEvent(e, false); + + /* textarea refresh handler */ + const refreshTextArea = () => { + if (!wrapRef.current) return; + if (textareaRef.current) wrapRef.current.removeChild(textareaRef.current); + const textarea = document.createElement("textarea"); + textarea.oninput = onChange; + textarea.addEventListener("keydown", onKeyDown); + textarea.addEventListener("keyup", onKeyUp); + textarea.placeholder = "채팅을 입력해주세요"; + textarea.value = ""; + textareaRef.current = textarea; + wrapRef.current.prepend(textarea); + resizeEvent(); + }; + useEffect(refreshTextArea, [sendMessage]); + + return ( +
textarea { + ${[ + css` + width: calc(100% - 30px); + height: 100%; + background: none; + border: none; + resize: none; + outline: none; + color: ${theme.black}; + padding: 8px 12px; + box-sizing: border-box; + `, + theme.font14, + ]} + } + `} + > + +
+ ); +}; + +export default BodyText; diff --git a/src/components/Chat/MessageForm/InputText/ButtonSend.tsx b/packages/web/src/components/Chat/MessageForm/InputText/ButtonSend.tsx similarity index 97% rename from src/components/Chat/MessageForm/InputText/ButtonSend.tsx rename to packages/web/src/components/Chat/MessageForm/InputText/ButtonSend.tsx index 79a3d00b7..cf66403ed 100644 --- a/src/components/Chat/MessageForm/InputText/ButtonSend.tsx +++ b/packages/web/src/components/Chat/MessageForm/InputText/ButtonSend.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; import ArrowUpwardRoundedIcon from "@mui/icons-material/ArrowUpwardRounded"; import MotionPhotosOnIcon from "@mui/icons-material/RotateLeftRounded"; diff --git a/src/components/Chat/MessageForm/InputText/index.tsx b/packages/web/src/components/Chat/MessageForm/InputText/index.tsx similarity index 91% rename from src/components/Chat/MessageForm/InputText/index.tsx rename to packages/web/src/components/Chat/MessageForm/InputText/index.tsx index 471fd01ed..936a8a3f9 100644 --- a/src/components/Chat/MessageForm/InputText/index.tsx +++ b/packages/web/src/components/Chat/MessageForm/InputText/index.tsx @@ -1,9 +1,9 @@ -import useSendMessage from "hooks/chat/useSendMessage"; +import useSendMessage from "@/hooks/chat/useSendMessage"; import BodyImage from "./BodyImage"; import BodyText from "./BodyText"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; type InputTextProps = { uploadedImage?: Nullable; diff --git a/src/components/Chat/MessageForm/NewMessage.tsx b/packages/web/src/components/Chat/MessageForm/NewMessage.tsx similarity index 96% rename from src/components/Chat/MessageForm/NewMessage.tsx rename to packages/web/src/components/Chat/MessageForm/NewMessage.tsx index 42f02d162..150117f94 100644 --- a/src/components/Chat/MessageForm/NewMessage.tsx +++ b/packages/web/src/components/Chat/MessageForm/NewMessage.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; import ArrowDownwardRoundedIcon from "@mui/icons-material/ArrowDownwardRounded"; diff --git a/src/components/Chat/MessageForm/ToolSheet/ToolButton.tsx b/packages/web/src/components/Chat/MessageForm/ToolSheet/ToolButton.tsx similarity index 95% rename from src/components/Chat/MessageForm/ToolSheet/ToolButton.tsx rename to packages/web/src/components/Chat/MessageForm/ToolSheet/ToolButton.tsx index 1c2d74229..090d3a00a 100644 --- a/src/components/Chat/MessageForm/ToolSheet/ToolButton.tsx +++ b/packages/web/src/components/Chat/MessageForm/ToolSheet/ToolButton.tsx @@ -1,6 +1,6 @@ -import useHoverProps from "hooks/theme/useHoverProps"; +import useHoverProps from "@/hooks/theme/useHoverProps"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; import AccountBalanceWalletRoundedIcon from "@mui/icons-material/AccountBalanceWalletRounded"; import ImageRoundedIcon from "@mui/icons-material/ImageRounded"; diff --git a/src/components/Chat/MessageForm/ToolSheet/index.tsx b/packages/web/src/components/Chat/MessageForm/ToolSheet/index.tsx similarity index 92% rename from src/components/Chat/MessageForm/ToolSheet/index.tsx rename to packages/web/src/components/Chat/MessageForm/ToolSheet/index.tsx index 03b392e35..15919dac2 100644 --- a/src/components/Chat/MessageForm/ToolSheet/index.tsx +++ b/packages/web/src/components/Chat/MessageForm/ToolSheet/index.tsx @@ -7,24 +7,24 @@ import { useState, } from "react"; -import useAccountFromChats from "hooks/chat/useAccountFromChats"; -import { useValueRecoilState } from "hooks/useFetchRecoilState"; -import useIsTimeOver from "hooks/useIsTimeOver"; +import useAccountFromChats from "@/hooks/chat/useAccountFromChats"; +import { useValueRecoilState } from "@/hooks/useFetchRecoilState"; +import useIsTimeOver from "@/hooks/useIsTimeOver"; -import AdaptiveDiv from "components/AdaptiveDiv"; +import AdaptiveDiv from "@/components/AdaptiveDiv"; import { ModalChatPayement, ModalChatSaveAccount, ModalChatSettlement, -} from "components/ModalPopup"; +} from "@/components/ModalPopup"; import ToolButton from "./ToolButton"; -import alertAtom from "atoms/alert"; +import alertAtom from "@/atoms/alert"; import { useSetRecoilState } from "recoil"; -import { dayNowClient, dayServerToClient } from "tools/day"; -import theme from "tools/theme"; +import { dayNowClient, dayServerToClient } from "@/tools/day"; +import theme from "@/tools/theme"; type ToolSheetProps = { roomInfo: Nullable; diff --git a/src/components/Chat/MessageForm/ToolSheetOpenButton.tsx b/packages/web/src/components/Chat/MessageForm/ToolSheetOpenButton.tsx similarity index 94% rename from src/components/Chat/MessageForm/ToolSheetOpenButton.tsx rename to packages/web/src/components/Chat/MessageForm/ToolSheetOpenButton.tsx index 71ccea87d..81bc02e65 100644 --- a/src/components/Chat/MessageForm/ToolSheetOpenButton.tsx +++ b/packages/web/src/components/Chat/MessageForm/ToolSheetOpenButton.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; import AddRoundedIcon from "@mui/icons-material/AddRounded"; diff --git a/src/components/Chat/MessageForm/index.css b/packages/web/src/components/Chat/MessageForm/index.css similarity index 100% rename from src/components/Chat/MessageForm/index.css rename to packages/web/src/components/Chat/MessageForm/index.css diff --git a/src/components/Chat/MessageForm/index.tsx b/packages/web/src/components/Chat/MessageForm/index.tsx similarity index 87% rename from src/components/Chat/MessageForm/index.tsx rename to packages/web/src/components/Chat/MessageForm/index.tsx index d63cc96d7..6cfd22418 100644 --- a/src/components/Chat/MessageForm/index.tsx +++ b/packages/web/src/components/Chat/MessageForm/index.tsx @@ -1,9 +1,9 @@ import { RefObject, memo, useState } from "react"; -import type { Chats, LayoutType } from "types/chat"; +import type { Chats, LayoutType } from "@/types/chat"; -import useAccountFromChats from "hooks/chat/useAccountFromChats"; -import useSendMessage from "hooks/chat/useSendMessage"; +import useAccountFromChats from "@/hooks/chat/useAccountFromChats"; +import useSendMessage from "@/hooks/chat/useSendMessage"; import InputText from "./InputText"; import NewMessage from "./NewMessage"; @@ -11,11 +11,11 @@ import ToolSheet from "./ToolSheet"; import ToolSheetOpenButton from "./ToolSheetOpenButton"; import "./index.css"; -import isVirtualKeyboardDetectedAtom from "atoms/isVirtualKeyboardDetected"; +import isVirtualKeyboardDetectedAtom from "@/atoms/isVirtualKeyboardDetected"; import { useRecoilValue } from "recoil"; -import { scrollToBottom } from "tools/chat/scroll"; -import theme from "tools/theme"; +import { scrollToBottom } from "@/tools/chat/scroll"; +import theme from "@/tools/theme"; type MessageFormProps = { layoutType: LayoutType; diff --git a/src/components/Chat/MessagesBody/ImageFullscreen.jsx b/packages/web/src/components/Chat/MessagesBody/ImageFullscreen.jsx similarity index 97% rename from src/components/Chat/MessagesBody/ImageFullscreen.jsx rename to packages/web/src/components/Chat/MessagesBody/ImageFullscreen.jsx index 8effdf74b..3affff458 100644 --- a/src/components/Chat/MessagesBody/ImageFullscreen.jsx +++ b/packages/web/src/components/Chat/MessagesBody/ImageFullscreen.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; diff --git a/src/components/Chat/MessagesBody/LoadingChats.tsx b/packages/web/src/components/Chat/MessagesBody/LoadingChats.tsx similarity index 87% rename from src/components/Chat/MessagesBody/LoadingChats.tsx rename to packages/web/src/components/Chat/MessagesBody/LoadingChats.tsx index 67fc8a3f7..6d5663c26 100644 --- a/src/components/Chat/MessagesBody/LoadingChats.tsx +++ b/packages/web/src/components/Chat/MessagesBody/LoadingChats.tsx @@ -1,4 +1,4 @@ -import Loading from "components/Loading"; +import Loading from "@/components/Loading"; const LoadingChats = () => (
{ + const style = { width: "210px", padding: "10px" }; + const styleText = { + wordBreak: "break-all" as any, + whiteSpace: "pre-line" as any, + ...theme.font14, + color, + }; + return ( +
+
+ 아직 정산 시작을 하지 않았거나 송금을 완료하지 않은 사용자가 있습니다. +
+
+ 좌측하단의 + 버튼을 눌러 뜨는 정산하기 또는{" "} + 송금하기 버튼을 눌러 정산 요청 또는 송금을 완료해주세요. +
+
+ ); +}; + +export default MessageArrival; diff --git a/packages/web/src/components/Chat/MessagesBody/MessageSet/MessageDeparture.tsx b/packages/web/src/components/Chat/MessagesBody/MessageSet/MessageDeparture.tsx new file mode 100644 index 000000000..4a8c852d7 --- /dev/null +++ b/packages/web/src/components/Chat/MessagesBody/MessageSet/MessageDeparture.tsx @@ -0,0 +1,45 @@ +import { useState } from "react"; + +import { ModalCallTaxi } from "@/components/ModalPopup"; + +import Button from "./Button"; + +import theme from "@/tools/theme"; + +type MessageDepartureProps = { + roomInfo: Room; + minutes: string; + color: CSS["color"]; +}; + +const MessageDeparture = ({ + roomInfo, + minutes, + color, +}: MessageDepartureProps) => { + const [isOpenCallTaxi, setIsOpenCallTaxi] = useState(false); + const style = { width: "210px", padding: "10px" }; + const styleText = { + marginBottom: "10px", + wordBreak: "break-all" as any, + whiteSpace: "pre-line" as any, + ...theme.font14, + color, + }; + return ( +
+
+ 택시 출발 {minutes}분 전 입니다. 동승자들이 모두 모였다면 택시를 호출한 + 후 출발하세요. +
+ + +
+ ); +}; + +export default MessageDeparture; diff --git a/src/components/Chat/MessagesBody/MessageSet/MessageImage.tsx b/packages/web/src/components/Chat/MessagesBody/MessageSet/MessageImage.tsx similarity index 89% rename from src/components/Chat/MessagesBody/MessageSet/MessageImage.tsx rename to packages/web/src/components/Chat/MessagesBody/MessageSet/MessageImage.tsx index f3f8d47fb..3f3b8c5c6 100644 --- a/src/components/Chat/MessagesBody/MessageSet/MessageImage.tsx +++ b/packages/web/src/components/Chat/MessagesBody/MessageSet/MessageImage.tsx @@ -1,10 +1,10 @@ -import useIsReadyToLoadImage from "hooks/chat/useIsReadyToLoadImage"; +import useIsReadyToLoadImage from "@/hooks/chat/useIsReadyToLoadImage"; -import alertAtom from "atoms/alert"; +import alertAtom from "@/atoms/alert"; import { useSetRecoilState } from "recoil"; -import theme from "tools/theme"; -import { getS3Url } from "tools/trans"; +import theme from "@/tools/theme"; +import { getS3Url } from "@/tools/trans"; import ImageNotSupportedRoundedIcon from "@mui/icons-material/ImageNotSupportedRounded"; import ImageRoundedIcon from "@mui/icons-material/ImageRounded"; diff --git a/src/components/Chat/MessagesBody/MessageSet/MessagePaySettlement.tsx b/packages/web/src/components/Chat/MessagesBody/MessageSet/MessagePaySettlement.tsx similarity index 95% rename from src/components/Chat/MessagesBody/MessageSet/MessagePaySettlement.tsx rename to packages/web/src/components/Chat/MessagesBody/MessageSet/MessagePaySettlement.tsx index a36563d83..79c880b6d 100644 --- a/src/components/Chat/MessagesBody/MessageSet/MessagePaySettlement.tsx +++ b/packages/web/src/components/Chat/MessagesBody/MessageSet/MessagePaySettlement.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; import CreditCardRoundedIcon from "@mui/icons-material/CreditCardRounded"; import SendRoundedIcon from "@mui/icons-material/SendRounded"; diff --git a/src/components/Chat/MessagesBody/MessageSet/MessageShare.tsx b/packages/web/src/components/Chat/MessagesBody/MessageSet/MessageShare.tsx similarity index 89% rename from src/components/Chat/MessagesBody/MessageSet/MessageShare.tsx rename to packages/web/src/components/Chat/MessagesBody/MessageSet/MessageShare.tsx index 66a931370..2725b0e7e 100644 --- a/src/components/Chat/MessagesBody/MessageSet/MessageShare.tsx +++ b/packages/web/src/components/Chat/MessagesBody/MessageSet/MessageShare.tsx @@ -1,10 +1,10 @@ import { useState } from "react"; -import { ModalRoomShare } from "components/ModalPopup"; +import { ModalRoomShare } from "@/components/ModalPopup"; import Button from "./Button"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; type MessageShareProps = { roomInfo: Room; diff --git a/src/components/Chat/MessagesBody/MessageSet/MessageText.tsx b/packages/web/src/components/Chat/MessagesBody/MessageSet/MessageText.tsx similarity index 91% rename from src/components/Chat/MessagesBody/MessageSet/MessageText.tsx rename to packages/web/src/components/Chat/MessagesBody/MessageSet/MessageText.tsx index 0c7201dd5..0cc97b215 100644 --- a/src/components/Chat/MessagesBody/MessageSet/MessageText.tsx +++ b/packages/web/src/components/Chat/MessagesBody/MessageSet/MessageText.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; type MessageTextProps = { text: string; diff --git a/src/components/Chat/MessagesBody/MessageSet/index.tsx b/packages/web/src/components/Chat/MessagesBody/MessageSet/index.tsx similarity index 83% rename from src/components/Chat/MessagesBody/MessageSet/index.tsx rename to packages/web/src/components/Chat/MessagesBody/MessageSet/index.tsx index e603f71ef..5bd3cf8f5 100644 --- a/src/components/Chat/MessagesBody/MessageSet/index.tsx +++ b/packages/web/src/components/Chat/MessagesBody/MessageSet/index.tsx @@ -1,22 +1,24 @@ import { memo, useCallback } from "react"; -import type { BotChat, LayoutType, UserChat } from "types/chat"; +import type { BotChat, LayoutType, UserChat } from "@/types/chat"; -import { useValueRecoilState } from "hooks/useFetchRecoilState"; +import { useValueRecoilState } from "@/hooks/useFetchRecoilState"; -import ProfileImage from "components/User/ProfileImage"; +import ProfileImage from "@/components/User/ProfileImage"; import MessageAccount from "./MessageAccount"; +import MessageArrival from "./MessageArrival"; +import MessageDeparture from "./MessageDeparture"; import MessageImage from "./MessageImage"; import MessagePaySettlement from "./MessagePaySettlement"; import MessageShare from "./MessageShare"; import MessageText from "./MessageText"; -import { getChatUniquewKey } from "tools/chat/chats"; -import dayjs from "tools/day"; -import theme from "tools/theme"; +import { getChatUniquewKey } from "@/tools/chat/chats"; +import dayjs from "@/tools/day"; +import theme from "@/tools/theme"; -import { ReactComponent as TaxiIcon } from "static/assets/TaxiAppIcon.svg"; +import { ReactComponent as TaxiIcon } from "@/static/assets/sparcsLogos/TaxiAppIcon.svg"; type MessageBodyProps = { type: (UserChat | BotChat)["type"]; @@ -38,6 +40,12 @@ const MessageBody = ({ type, content, roomInfo, color }: MessageBodyProps) => { return ; case "share": return ; + case "departure": + return ( + + ); + case "arrival": + return ; default: return null; } @@ -54,7 +62,7 @@ const MessageSet = ({ chats, layoutType, roomInfo }: MessageSetProps) => { const authorId = chats?.[0]?.authorId; const authorProfileUrl = "authorProfileUrl" in chats?.[0] ? chats?.[0].authorProfileUrl : ""; - const authorName = chats?.[0]?.authorName; + const authorName = "authorName" in chats?.[0] ? chats?.[0].authorName : ""; const style = { position: "relative" as any, @@ -104,7 +112,10 @@ const MessageSet = ({ chats, layoutType, roomInfo }: MessageSetProps) => { ? userOid === authorId ? theme.purple_dark : theme.gray_background - : type === "account" || type === "share" + : type === "account" || + type === "share" || + type === "departure" || + type === "arrival" ? layoutType === "sidechat" ? theme.purple_light : theme.white diff --git a/src/components/Chat/MessagesBody/index.tsx b/packages/web/src/components/Chat/MessagesBody/index.tsx similarity index 86% rename from src/components/Chat/MessagesBody/index.tsx rename to packages/web/src/components/Chat/MessagesBody/index.tsx index 339b55d26..45b90bb3c 100644 --- a/src/components/Chat/MessagesBody/index.tsx +++ b/packages/web/src/components/Chat/MessagesBody/index.tsx @@ -1,8 +1,8 @@ import { ForwardedRef, forwardRef } from "react"; -import type { Chats, LayoutType } from "types/chat"; +import type { Chats, LayoutType } from "@/types/chat"; -import useChatsForBody from "hooks/chat//useChatsForBody"; +import useChatsForBody from "@/hooks/chat//useChatsForBody"; import LoadingChats from "./LoadingChats"; diff --git a/src/components/Chat/index.tsx b/packages/web/src/components/Chat/index.tsx similarity index 84% rename from src/components/Chat/index.tsx rename to packages/web/src/components/Chat/index.tsx index 81f14f205..6b2cc6512 100644 --- a/src/components/Chat/index.tsx +++ b/packages/web/src/components/Chat/index.tsx @@ -1,14 +1,14 @@ import { useRef, useState } from "react"; import { useStateWithCallbackLazy } from "use-state-with-callback"; -import type { Chats, LayoutType } from "types/chat"; +import type { Chats, LayoutType } from "@/types/chat"; -import useBodyScrollControllerEffect from "hooks/chat/useBodyScrollControllerEffect"; -import useSendMessage from "hooks/chat/useSendMessage"; -import useSocketChatEffect from "hooks/chat/useSocketChatEffect"; -import useDateToken from "hooks/useDateToken"; -import useDisableScrollEffect from "hooks/useDisableScrollEffect"; -import useQuery from "hooks/useTaxiAPI"; +import useBodyScrollControllerEffect from "@/hooks/chat/useBodyScrollControllerEffect"; +import useSendMessage from "@/hooks/chat/useSendMessage"; +import useSocketChatEffect from "@/hooks/chat/useSocketChatEffect"; +import useDateToken from "@/hooks/useDateToken"; +import useDisableScrollEffect from "@/hooks/useDisableScrollEffect"; +import useQuery from "@/hooks/useTaxiAPI"; import Container from "./Container"; import Header from "./Header"; diff --git a/src/components/DottedLine.tsx b/packages/web/src/components/DottedLine.tsx similarity index 75% rename from src/components/DottedLine.tsx rename to packages/web/src/components/DottedLine.tsx index 610e7581c..ed2a0704d 100644 --- a/src/components/DottedLine.tsx +++ b/packages/web/src/components/DottedLine.tsx @@ -1,4 +1,6 @@ -import theme from "tools/theme"; +import { HTMLAttributes } from "react"; + +import theme from "@/tools/theme"; type Direction = "row" | "column"; @@ -8,9 +10,13 @@ type LineProps = { Margin, `${PixelValue}` | `${PixelValue} ${PixelValue} ${PixelValue} ${PixelValue}` >; -}; +} & HTMLAttributes; -const DottedLine = ({ direction = "row", margin = "0 0" }: LineProps) => { +const DottedLine = ({ + direction = "row", + margin = "0 0", + ...divProps +}: LineProps) => { const wrapper = { height: direction === "row" ? "1px" : undefined, width: @@ -33,9 +39,13 @@ const DottedLine = ({ direction = "row", margin = "0 0" }: LineProps) => { direction === "column" ? `5px dotted ${theme.gray_line}` : undefined, marginTop: direction === "column" ? "-2px" : undefined, }; + return ( -
-
+
+
); }; diff --git a/packages/web/src/components/Empty.tsx b/packages/web/src/components/Empty.tsx new file mode 100644 index 000000000..706023d4d --- /dev/null +++ b/packages/web/src/components/Empty.tsx @@ -0,0 +1,45 @@ +import { HTMLProps, ReactNode } from "react"; + +import theme from "@/tools/theme"; + +import NotInterestedIcon from "@mui/icons-material/NotInterested"; + +type EmptyProps = { + type: "mobile" | "pc"; + children?: ReactNode; + className?: string; +} & HTMLProps; + +const Empty = ({ type, children, className, ...divProps }: EmptyProps) => { + const styleCommon = { + display: "flex", + justifyContent: "center", + ...theme.font14_bold, + color: theme.gray_text, + columnGap: "6px", + }; + const styleMobile = { + ...styleCommon, + padding: "24px 0", + borderRadius: "12px", + backgroundColor: theme.gray_background, + border: "0.25px solid " + theme.gray_line, + }; + const stylePC = { + ...styleCommon, + padding: "48px 0 26px", + }; + + return ( +
+ + {children} +
+ ); +}; + +export default Empty; diff --git a/packages/web/src/components/Event/BodyRandomBox/index.css b/packages/web/src/components/Event/BodyRandomBox/index.css new file mode 100644 index 000000000..2c07e414a --- /dev/null +++ b/packages/web/src/components/Event/BodyRandomBox/index.css @@ -0,0 +1,92 @@ +.c2023fallevent-randombox { + position: relative; + width: 500px; + height: 500px; + transform-style: preserve-3d; + will-change: transform; +} + +.c2023fallevent-randombox-side { + position: absolute; + width: 100%; + height: 100%; + cursor: pointer; + transform-style: preserve-3d; + overflow: hidden; + will-change: transform; +} + +.c2023fallevent-randombox-side-top { + transform-origin: 0px 0px 0px; + top: -24px; + left: 19px; + transform: scale(1.02) rotateX(-25deg) rotateY(-25deg) translateZ(-250px) + rotateX(90deg); +} + +.c2023fallevent-emoji { + position: absolute; + width: 500px; + height: 500px; + border-radius: 8px; + overflow: hidden; + font-size: 250px; + transform: rotateX(-25deg) rotateY(-25deg) translate(calc(250px - 50%), 0rem); +} + +/* Open Box States */ + +.c2023fallevent-randombox-open .c2023fallevent-randombox-side { + cursor: auto; +} + +.c2023fallevent-randombox-open .c2023fallevent-randombox-side-top { + animation: openBox 14s ease-out forwards; +} + +.c2023fallevent-randombox-open .c2023fallevent-emoji { + opacity: 0; + animation: raiseEmoji 4s 4s ease-out forwards, + rotateEmoji 5s 8s linear infinite; +} + +/* Animations */ + +@keyframes openBox { + 20% { + transform: rotateX(-25deg) rotateY(-25deg) translateZ(-250px) rotateX(95deg); + } + 30% { + transform: rotateX(-25deg) rotateY(-25deg) translateZ(-250px) + rotateX(130deg); + } + 100% { + transform: rotateX(-25deg) rotateY(-25deg) translateZ(-250px) + rotateX(252deg); + } +} + +@keyframes raiseEmoji { + 0% { + opacity: 0; + } + 20% { + opacity: 0.5; + } + 100% { + opacity: 1; + transform: rotateX(-25deg) rotateY(-25deg) + translate(calc(250px - 50%), -400px); + } +} + +@keyframes rotateEmoji { + 0% { + transform: rotateX(-25deg) rotateY(-25deg) + translate(calc(250px - 50%), -400px) rotateY(0deg); + } + 100% { + transform: rotateX(-25deg) rotateY(-25deg) + translate(calc(250px - 50%), -400px) rotateY(360deg); + } +} diff --git a/packages/web/src/components/Event/BodyRandomBox/index.tsx b/packages/web/src/components/Event/BodyRandomBox/index.tsx new file mode 100644 index 000000000..0f73a735b --- /dev/null +++ b/packages/web/src/components/Event/BodyRandomBox/index.tsx @@ -0,0 +1,120 @@ +import { css, keyframes } from "@emotion/react"; +import { memo, useCallback, useEffect, useRef, useState } from "react"; + +import "./index.css"; + +import theme from "@/tools/theme"; + +import BackPlaneImage from "@/static/events/2023fallRandomboxBack.png"; +import FrontPlaneImage from "@/static/events/2023fallRandomboxFront.png"; +import FrontPlaneLightImage from "@/static/events/2023fallRandomboxFrontLight.png"; +import { ReactComponent as TopPlane } from "@/static/events/2023fallRandomboxTop.svg"; + +type BodyRandomBoxProps = { + isBoxOpend: boolean; + onClickBox?: () => void; + itemImageUrl?: string; +}; + +const BodyRandomBox = ({ + isBoxOpend, + onClickBox, + itemImageUrl, +}: BodyRandomBoxProps) => { + const bodyRef = useRef(null); + const getBodyWidth = useCallback(() => bodyRef.current?.clientWidth || 0, []); + const [bodyWidth, setBodyWidth] = useState(getBodyWidth()); + const boxSize = bodyWidth * 0.6; + + useEffect(() => { + const resizeEvent = () => setBodyWidth(getBodyWidth()); + resizeEvent(); + window.addEventListener("resize", resizeEvent); + return () => window.removeEventListener("resize", resizeEvent); + }, []); + + const stylePlane = { + position: "absolute" as const, + top: 0, + left: 0, + width: "100%", + height: "100%", + }; + const stylePlaneFront = { + position: "absolute" as const, + top: "-32px", + left: "-84px", + width: "670px", + }; + const stylePlaneBack = { + position: "absolute" as const, + top: "-120px", + left: "-80px", + width: "664px", + }; + const stylePlaneFlash = css` + animation: ${keyframes` + 0% { + opacity: 0; + } + 50% { + opacity: 1; + } + 100% { + opacity: 0; + } + `} 2s linear infinite; + `; + const styleItem = { + position: "absolute" as const, + top: "10%", + left: "10%", + width: "80%", + height: "80%", + }; + + return ( +
+
+ +
+
+ +
+ {isBoxOpend && ( +
+ {itemImageUrl && ( + item + )} +
+ )} +
+ + +
+
+ ); +}; + +export default memo(BodyRandomBox); diff --git a/packages/web/src/components/Event/CreditAmountStatusContainer/index.tsx b/packages/web/src/components/Event/CreditAmountStatusContainer/index.tsx new file mode 100644 index 000000000..40fbd2fa7 --- /dev/null +++ b/packages/web/src/components/Event/CreditAmountStatusContainer/index.tsx @@ -0,0 +1,74 @@ +import { useValueRecoilState } from "@/hooks/useFetchRecoilState"; + +import WhiteContainer from "@/components/WhiteContainer"; + +import theme from "@/tools/theme"; + +import { ReactComponent as CreditIcon } from "@/static/events/2023fallCredit.svg"; +import { ReactComponent as Ticket1Icon } from "@/static/events/2023fallTicket1.svg"; +import { ReactComponent as Ticket2Icon } from "@/static/events/2023fallTicket2.svg"; + +type CreditAmountStatusContainerProps = { + type?: "credit" | "ticket"; +} & Parameters[0]; + +const CreditAmountStatusContainer = ({ + type = "credit", + ...whiteContainerProps +}: CreditAmountStatusContainerProps) => { + const { creditAmount, ticket1Amount, ticket2Amount } = + useValueRecoilState("event2023FallInfo") || {}; + + return ( + +
+ {type === "credit" ? "내가 모은 송편" : "일반 / 고급 응모권"} +
+ {type === "credit" ? ( + <> + +
+ {creditAmount || 0} +
+ + ) : ( + <> + +
+ {ticket1Amount || 0} +
+
+ +
+ {ticket2Amount || 0} +
+ + )} + + ); +}; + +export default CreditAmountStatusContainer; diff --git a/packages/web/src/components/Event/CreditAmoutContainer/index.tsx b/packages/web/src/components/Event/CreditAmoutContainer/index.tsx new file mode 100644 index 000000000..b22367845 --- /dev/null +++ b/packages/web/src/components/Event/CreditAmoutContainer/index.tsx @@ -0,0 +1,62 @@ +import { useValueRecoilState } from "@/hooks/useFetchRecoilState"; + +import WhiteContainer from "@/components/WhiteContainer"; + +import eventTheme from "@/tools/eventTheme"; +import theme from "@/tools/theme"; + +import CoinIcon from "@/static/events/2024springCoin.gif"; + +type CreditAmountContainerProps = Parameters[0]; + +const CreditAmountContainer = ({ + ...whiteContainerProps +}: CreditAmountContainerProps) => { + const { creditAmount, group, groupCreditAmount } = + useValueRecoilState("event2024SpringInfo") || {}; + + return ( + +
+
+ 새터 {group}반 넙죽코인 +
+ coin +
+ {groupCreditAmount ? ("000" + groupCreditAmount).slice(-4) : "000"} +
+
+
+
+ 내가 획득한 넙죽코인 +
+ coin +
+ {creditAmount ? ("000" + creditAmount).slice(-4) : "000"} +
+
+
+ ); +}; + +export default CreditAmountContainer; diff --git a/packages/web/src/components/Event/EventButton/index.tsx b/packages/web/src/components/Event/EventButton/index.tsx new file mode 100644 index 000000000..77bd5cf1f --- /dev/null +++ b/packages/web/src/components/Event/EventButton/index.tsx @@ -0,0 +1,29 @@ +import { HTMLAttributes } from "react"; + +import eventTheme from "@/tools/eventTheme"; +import theme from "@/tools/theme"; + +type EventButtonProps = { + title: string; +} & HTMLAttributes; + +const EventButton = ({ title, ...divProps }: EventButtonProps) => { + return ( +
+ {title} +
+ ); +}; + +export default EventButton; diff --git a/packages/web/src/components/Event/RabbitAnimatedBackground/index.tsx b/packages/web/src/components/Event/RabbitAnimatedBackground/index.tsx new file mode 100644 index 000000000..1940dafa2 --- /dev/null +++ b/packages/web/src/components/Event/RabbitAnimatedBackground/index.tsx @@ -0,0 +1,85 @@ +import "./indexMoon.css"; +import "./indexRabbit.css"; + +const RabbitAnimatedBackground = () => { + return ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ); +}; + +export default RabbitAnimatedBackground; diff --git a/packages/web/src/components/Event/RabbitAnimatedBackground/indexMoon.css b/packages/web/src/components/Event/RabbitAnimatedBackground/indexMoon.css new file mode 100644 index 000000000..3fb00037f --- /dev/null +++ b/packages/web/src/components/Event/RabbitAnimatedBackground/indexMoon.css @@ -0,0 +1,108 @@ +.rabbit-animated-circle1 { + width: 450px; + height: 450px; + position: relative; + transform: scale(1.5) translateX(-70px) translateY(5px); +} +.rabbit-animated-circle2 { + width: 450px; + height: 450px; + position: relative; +} +.moon { + background: #ffa62b; + position: absolute; + width: 180px; + height: 180px; + top: 18%; + left: 40%; + border-radius: 100%; +} +.moon2 { + background: #ffe05d; + position: absolute; + width: 160px; + height: 160px; + top: 19%; + left: 44%; + border-radius: 100%; +} +.crater1 { + position: absolute; + background: #ffa62b; + width: 40px; + height: 40px; + top: 23%; + left: 61%; + border-radius: 100%; +} +.crater2 { + position: absolute; + background: #ffa62b; + width: 20px; + height: 20px; + top: 38%; + left: 56%; + border-radius: 100%; +} +.crater3 { + position: absolute; + background: #ffa62b; + width: 10px; + height: 10px; + top: 36%; + left: 70%; + border-radius: 100%; +} +.cloud1 { + background: white; + position: absolute; + top: 30%; + left: 10%; + height: 15px; + width: 90px; + border-radius: 50px; + opacity: 0.5; + animation: 5s linear infinite rabbit-animated-slidein; +} +.cloud2 { + background: white; + position: absolute; + top: 10%; + left: 10%; + height: 15px; + width: 190px; + border-radius: 50px; + opacity: 0.5; + animation: 7s reverse infinite rabbit-animated-slidein; +} +.cloud3 { + background: white; + position: absolute; + top: 42%; + left: 10%; + height: 20px; + width: 250px; + border-radius: 50px; + opacity: 0.5; + animation: 7s linear infinite rabbit-animated-slidein; +} +.cloud4 { + background: white; + position: absolute; + top: 50%; + left: 10%; + height: 15px; + width: 90px; + border-radius: 50px; + opacity: 0.5; + animation: 5s reverse infinite rabbit-animated-slidein; +} +@keyframes rabbit-animated-slidein { + from { + transform: translateX(-50%); + } + to { + transform: translateX(500%); + } +} diff --git a/packages/web/src/components/Event/RabbitAnimatedBackground/indexRabbit.css b/packages/web/src/components/Event/RabbitAnimatedBackground/indexRabbit.css new file mode 100644 index 000000000..aa72b9bac --- /dev/null +++ b/packages/web/src/components/Event/RabbitAnimatedBackground/indexRabbit.css @@ -0,0 +1,8237 @@ +.bar { + width: 800px; + height: 600px; + position: relative; + /* background: linear-gradient(to right, #1e3c72, #2a5298); */ + --animation: 6s infinite ease; +} +.bar *, +.bar *:before, +.bar *:after { + content: ""; + position: absolute; +} + +.balcony { + bottom: 0; + left: 0px; + width: 800px; + height: 243px; + background: #03060b; + border-top: 17px solid #3c4156; +} +.back-sofa { + top: 345px; + left: 462px; + width: 210px; + height: 200px; + box-shadow: inset 22px 0 #242839, inset 0 -164px #131824, + inset 0 -175px #242839; +} +.back-sofa:before { + top: -24px; + left: 286px; + width: 50px; + height: 50px; + background: #2d2e41; + border-radius: 14px 37px 10px 0; +} +.back-sofa > div:nth-child(1) { + top: 24px; + left: 35px; + width: 80px; + height: 60px; + background: #2d2e41; + border-radius: 100% 0 0 22px; +} +.back-sofa > div:nth-child(1):before { + top: -40px; + left: 29px; + width: 120px; + height: 100px; + background: #2d2e41; + border-radius: 80px 0 0 0; +} +.back-sofa > div:nth-child(1):after { + top: -55px; + left: 180px; + width: 170px; + height: 101px; + background: #2d2e41; + border-radius: 20px 0 0 0; + border-bottom: 3px solid #fdec66; + transform-origin: bottom left; + transform: skewY(21deg); +} +.back-sofa > div:nth-child(2) { + top: 77px; + left: 13px; + width: 310px; + height: 68px; + box-sizing: border-box; + background: #303447; + border-radius: 18px 70% 0 6px; +} +.back-sofa > div:nth-child(2):before { + top: 68px; + left: 5px; + width: 240px; + height: 110px; + background: #303447; + transform-origin: top left; + transform: skewX(62deg); +} +.back-sofa > div:nth-child(2):after { + top: 100px; + left: 225px; + width: 20px; + height: 80px; + background: #383b53; + border-radius: 5px 0 0 0; +} +.back-sofa > div:nth-child(3) { + top: 77px; + left: 22px; + width: 240px; + height: 20px; + box-sizing: border-box; + background-image: linear-gradient(-152deg, #2d2e41 89%, transparent 89%); + border-top: 1px solid #fdec66; + border-right: 0; + border-radius: 5px 0 0 0; + transform-origin: top left; +} +.back-sofa > div:nth-child(3):before { + top: 0; + left: -13px; + width: 228px; + height: 98px; + background: #2d2e41; + border-radius: 9% 0 0 5px; + border-left: 3px solid #fdec66; + border-right: 3px solid #fdec66; + border-bottom: 1px solid #fdec66; + transform-origin: top left; + transform: skewX(66.5deg); +} +.back-sofa > div:nth-child(6) { + top: 90px; + left: 253px; + width: 100px; + height: 170px; + box-shadow: inset 25px 0 #202335, inset 0 -62px #111521, inset 0 -73px #202335, + inset 0 -140px #111521, inset 0 -152px #202335; + z-index: 1; +} +.back-sofa .coctail { + top: -43px; + left: 108px; + width: 3px; + height: 38px; + background: #fffff8; +} +.back-sofa .coctail:before { + top: -41px; + left: -17px; + width: 36px; + height: 36px; + background-image: linear-gradient( + -45deg, + transparent 4%, + #e16858 4%, + #e16858 31%, + #fffff8 31%, + #fffff8 50%, + transparent 50% + ); + transform: rotate(45deg); +} +.back-sofa .coctail:after { + top: -44px; + left: 4px; + width: 3px; + height: 31px; + background: #fef76c; + transform-origin: bottom left; + transform: skewX(-22deg); +} +.back-sofa .coctail > div { + top: -34px; + left: 6px; + width: 10px; + height: 14px; + background: #67ce6f; + border-radius: 50%; + box-shadow: inset -1px 0px 0 3px #5ca348; + transform: rotate(30deg); + z-index: 1; +} +.table { + top: 491px; + left: 234px; + z-index: 1; +} +.table:before { + top: 58px; + left: -10px; + width: 173px; + height: 60px; + border-radius: 20px 0 0 0; + background-image: linear-gradient(70deg, #2a2c3d 92%, transparent 92%); + transform-origin: top left; + transform: skewX(54deg); + box-shadow: inset 117px 0 0 -100px #141829; + z-index: -1; +} +.table:after { + top: 0px; + left: 0px; + width: 15px; + height: 110px; + background: #141829; + box-shadow: 98px 67px #141829, 275px 67px #141829; +} +.table > div:nth-child(1) { + top: -40px; + left: -19px; + width: 202px; + height: 80px; + border-radius: 0 0 5px 4px; + background-image: linear-gradient(65deg, #2d2e41 84%, transparent 0%); + transform-origin: top left; + transform: skewX(57deg); + z-index: 1; +} +.table > div:nth-child(1):before { + top: 0px; + left: 0px; + width: 124px; + height: 41px; + background: #141829; + transform-origin: top left; + transform: skewX(-57deg) skewY(32.6deg); + z-index: -1; +} +.table > div:nth-child(1):after { + top: 80px; + left: -31px; + width: 199px; + height: 40px; + background: #141829; + transform: skewX(-57deg); + z-index: -1; +} +.lightning { + top: -26px; + left: 105px; + width: 22px; + height: 28px; + background: #d1742f; + box-shadow: 2px -42px 0 3px #f9e753, 12px -42px 0 3px #f9e753; + border-bottom: 2px solid #aa6337; + z-index: 1; +} +.lightning:before { + top: -5px; + left: -8px; + width: 8px; + height: 28px; + background: #e19c46; + border-bottom: 2px solid #d1742f; + transform-origin: top left; + transform: skewY(33deg); +} +.lightning:after { + top: -166px; + left: -124px; + width: 260px; + height: 250px; + border-radius: 50%; + background-image: radial-gradient(#fdf690 0%, transparent 65%); + animation: lightning 5s ease infinite; + opacity: 0.4; +} +.lightning > div:nth-child(1) { + top: 31px; + left: 0px; + width: 25px; + height: 3px; + background: #aa6337; +} +.lightning > div:nth-child(1):before { + top: -36px; + left: -8px; + width: 22px; + height: 5px; + background: #eebf4f; + transform-origin: top left; + transform: skewX(57deg); +} +.lightning > div:nth-child(1):after { + top: -7px; + left: -11px; + width: 11px; + height: 3px; + background: #aa6337; + transform-origin: top left; + transform: skewY(33deg); +} +.lightning > div:nth-child(2) { + top: -9px; + left: 5px; + width: 4px; + height: 7px; + background: #f9edbe; +} +.lightning > div:nth-child(2):before { + top: -49px; + left: -27px; + width: 21px; + height: 36px; + background: #fdec66; + transform-origin: top left; + transform: skewY(32deg); +} +.lightning > div:nth-child(2):after { + top: -49px; + left: -27px; + width: 38px; + height: 13px; + background: #f7dc4b; + transform-origin: top left; + transform: skewX(58deg); +} +.rabbit-1 { + top: 0px; + left: 0px; + z-index: 1; +} +.rabbit-1__body { + top: -32px; + left: 107px; + width: 193px; + height: 182px; + background: #ece1b4; + border-radius: 50%; + animation: rabbit1Body var(--animation); + z-index: 2; +} +.rabbit-1__body:before { + top: -6px; + left: 126px; + width: 65px; + height: 140px; + background: #ece1b4; + border-radius: 0 30% 50% 0; + transform-origin: bottom left; + transform: rotate(11deg); + animation: rabbit1BodyBefore var(--animation); +} +.rabbit-1__body:after { + top: 10px; + left: 0px; + width: 158px; + height: 163px; + background: #f9edbe; + border-radius: 47% 53% 50% 50%; + animation: rabbit1BodyAfter var(--animation); + z-index: 1; +} +.rabbit-1__back-leg { + top: 41px; + left: 66px; + width: 70px; + height: 36px; + background: #e7dca3; + border-radius: 100% 0 0 10%; + transform-origin: top right; + transform: rotate(-8deg); + animation: rabbit1BackLeg var(--animation); +} +.rabbit-1__back-leg:before { + top: 33px; + left: -3px; + width: 70px; + height: 36px; + background: #e7dca3; + border-radius: 0 0 0 33%; +} +.rabbit-1__back-leg:after { + top: 12px; + left: 0px; + width: 18px; + height: 59px; + background: #e7dca3; + border-radius: 14px 50% 0 20px; + transform-origin: left bottom; + transform: rotate(-18deg); + animation: rabbit1BackLegAfter var(--animation); +} +.rabbit-1__leg { + top: 50px; + left: 166px; + width: 110px; + height: 75px; + background: #ece1b4; + border-radius: 54% 100% 38% 54%; + transform-origin: bottom right; + transform: rotate(-27deg); + animation: rabbit1Leg var(--animation); + z-index: 2; +} +.rabbit-1__leg:before { + top: 0px; + left: -4px; + width: 20px; + height: 62px; + background: #ece1b4; + border-radius: 70% 130% 10% 30px; + transform-origin: bottom right; + transform: rotate(-9deg); + animation: rabbit1LegBefore var(--animation); +} +.rabbit-1__leg:after { + bottom: 0px; + left: -6px; + width: 40px; + height: 50px; + background: #ece1b4; + border-radius: 50% 0 0 50%; + transform-origin: bottom right; + transform: rotate(0deg); +} +.rabbit-1__arm1 { + top: -22px; + left: 109px; + width: 55px; + height: 25px; + background: #f9edbe; + border-radius: 100% 2px 2px 100%; + transform-origin: top right; + transform: rotate(-11deg); + animation: rabbit1Arm1 var(--animation); + z-index: 1; +} +.rabbit-1__arm1:before { + top: 0px; + left: -19px; + width: 46px; + height: 23px; + background: #f9edbe; + border-radius: 100% 2px 2px 40%; + transform-origin: right center; + transform: rotate(8deg); + animation: rabbit1Arm1Before var(--animation); +} +.rabbit-1__arm2 { + top: -18px; + left: 301px; + width: 36px; + height: 25px; + background: #f9edbe; + border-radius: 50% 50% 50% 50%; + animation: rabbit1Arm2 var(--animation); + z-index: 2; +} +.rabbit-1__arm2:before { + top: 5px; + left: 21px; + width: 19px; + height: 42px; + border-radius: 100%; + background: #f9edbe; + transform-origin: top center; + transform: rotate(10deg); + animation: rabbit1Arm2Before var(--animation); +} +.rabbit-1__head { + top: -109px; + left: 162px; + animation: rabbit1Head var(--animation); + z-index: 2; +} +.rabbit-1__head:before { + top: 0; + left: 0; + width: 154px; + height: 106px; + background: #ece1b4; + border-radius: 74px 80px 10px 32px; +} +.rabbit-1__head:after { + top: 38px; + left: 142px; + width: 70px; + height: 50px; + border-radius: 0 100%; + background: #ece1b4; + transform: rotate(48deg); + transform-origin: top left; + animation: rabbit1HeadAfter var(--animation); +} +.rabbit-1__ear1 { + top: -69px; + left: 53px; + width: 20px; + height: 80px; + background: #eda294; + border-radius: 5px 19px 100% 10px; + box-shadow: inset -9px 2px #ece0b1; + transform-origin: bottom center; + transform: rotate(-40deg); + animation: rabbit1Ear1 var(--animation); + z-index: -1; +} +.rabbit-1__ear1:before { + top: -8px; + right: 3px; + width: 89px; + height: 25px; + background: #ece0b1; + border-radius: 100%; + transform-origin: center right; + transform: rotate(-20deg); + animation: rabbit1Ear1Before var(--animation); +} +.rabbit-1__ear2 { + top: -57px; + left: 93px; + width: 22px; + height: 76px; + background: #eda294; + border-radius: 10px 50% 90% 0; + box-shadow: inset -10px 7px #ece1b4; + transform-origin: bottom center; + transform: rotate(8deg); + animation: rabbit1Ear2 var(--animation); + z-index: -1; +} +.rabbit-1__ear2 > div { + top: -72px; + left: 1px; + width: 26px; + height: 84px; + background: #ece1b4; + border-radius: 100%; + transform-origin: 22px 80px; + transform: rotate(-34deg); + overflow: hidden; + animation: rabbit1Ear2Div var(--animation); +} +.rabbit-1__ear2 > div:before { + top: 6px; + left: -39px; + width: 50px; + height: 80px; + background: #eda294; + transform: rotate(5deg); + border-radius: 0 14% 15px 0; + animation: rabbit1Ear2DivBefore var(--animation); +} +.rabbit-1__mouth { + top: 66px; + left: 38px; + animation: rabbit1Mouth var(--animation); +} +.rabbit-1__mouth:before { + top: 0px; + left: 4px; + width: 11px; + height: 7px; + background: #555458; + border-radius: 0 0 3px 3px; + box-shadow: inset 55px 50px 0 -50px #fffff8, inset -55px 50px 0 -50px #fffff8; + z-index: 1; +} +.rabbit-1__mouth:after { + top: -7px; + left: -9px; + width: 41px; + height: 7px; + background: #ebe2b3; + border-radius: 50%; + z-index: 1; + animation: rabbit1MouthAfter var(--animation); +} +.rabbit-1__mouth > div { + top: 0; + left: 0; + width: 22px; + height: 23px; + background: #555458; + border-radius: 4px 4px 12px 10px; + overflow: hidden; + animation: rabbit1MouthDiv var(--animation); +} +.rabbit-1__mouth > div:before { + top: 13px; + left: 9px; + width: 15px; + height: 20px; + border-radius: 50%; + background: #e16858; +} +.rabbit-1__eye1 { + top: 39px; + left: 18px; + width: 6px; + height: 12px; + background: #555458; + border-radius: 50%; + transform-origin: bottom center; + transform: rotate(13deg); + animation: rabbit1Eye1 var(--animation); +} +.rabbit-1__eye1:before { + top: -15px; + left: 0px; + width: 13px; + height: 7px; + border-radius: 50%; + box-shadow: inset -1px 2px 0 0 #555458; + transform: rotate(-39deg); + animation: rabbit1Eye1Before var(--animation); +} +.rabbit-1__eye1:after { + top: -7px; + left: -5px; + width: 14px; + height: 7px; + border-radius: 50%; + background: #ebe2b3; + transform: rotate(-9deg); + animation: rabbit1Eye1After var(--animation); +} +.rabbit-1__eye2 { + top: 50px; + left: 69px; + width: 6px; + height: 12px; + background: #555458; + border-radius: 50%; + transform-origin: bottom center; + transform: rotate(13deg); + animation: rabbit1Eye2 var(--animation); +} +.rabbit-1__eye2:before { + top: -21px; + left: 0px; + width: 14px; + height: 12px; + border-radius: 50%; + box-shadow: inset -1px 8px 0 -5px #555458; + transform: rotate(21deg); + animation: rabbit1Eye2Before var(--animation); +} +.rabbit-1__eye2:after { + top: -7px; + left: -5px; + width: 14px; + height: 7px; + border-radius: 50%; + background: #ebe2b3; + transform: rotate(-9deg); + animation: rabbit1Eye2After var(--animation); +} +.rabbit-1__nose { + top: 50px; + left: 37px; + width: 17px; + height: 6px; + background: #ff908c; + border-radius: 50%; + transform: rotate(11deg); + animation: rabbit1Nose var(--animation); +} +.rabbit-1__nose:before { + top: -4px; + left: 1px; + width: 14px; + height: 14px; + background-image: linear-gradient(-45deg, #ff908c 50%, transparent 50%); + border-radius: 4px; + transform: rotate(45deg); +} +.front-sofa { + top: 345px; + left: 0; + width: 30px; + height: 260px; + background: #303045; +} +.front-sofa:before { + top: 0; + left: 0; + width: 30px; + height: 260px; + background: #303045; + z-index: 2; +} +.front-sofa:after { + top: 77px; + left: -5px; + width: 245px; + height: 170px; + box-shadow: inset 25px 0 #202335, inset -22px 0 #202335, inset 0 -42px #111521, + inset 0 -52px #202335, inset 0 -119px #111521, inset 0 -131px #202335; + z-index: 2; +} +.front-sofa > div:nth-child(2) { + top: -40px; + left: -29px; + width: 67px; + height: 66px; + background: #303447; + border-right: 3px solid #fdec66; + border-radius: 0 0 5px 0; + transform: skewX(52deg); + z-index: 2; +} +.front-sofa > div:nth-child(2):before { + top: 61px; + left: -59px; + width: 72px; + height: 100px; + background: #3c4156; + border-radius: 7px 17px 0 0; + transform: skewX(-51deg); +} +.front-sofa > div:nth-child(2):after { + top: 61px; + left: -91px; + width: 51px; + height: 101px; + background: #3c4156; + border-radius: 0 9px 0 0; + border-right: 2px solid #fdec66; + transform-origin: bottom left; + transform: skewX(-48deg); +} +.front-sofa .coctail { + top: -17px; + left: 57px; + width: 3px; + height: 48px; + background: #fffff8; + transform: rotate(-24deg); + transform-origin: bottom center; +} +.front-sofa .coctail:before { + top: -32px; + left: -15px; + width: 12px; + height: 14px; + background: #67ce6f; + border-radius: 50%; + box-shadow: inset 1px -2px 0 4px #5ca348; + transform: rotate(-20deg); + animation: frontSofaCoctailBefore var(--animation); +} +.front-sofa .coctail:after { + top: 44px; + left: -10px; + border-bottom: 7px solid #fffff8; + border-right: 11px solid transparent; + border-left: 11px solid transparent; +} +.front-sofa .coctail > div:nth-child(1) { + top: -75px; + left: -32px; + width: 46px; + height: 46px; + background-image: linear-gradient( + -45deg, + rgba(255, 255, 255, 0.5) 40%, + transparent 40% + ); + transform-origin: bottom left; + transform: rotate(45deg); + overflow: hidden; +} +.front-sofa .coctail > div:nth-child(1):after { + top: 13px; + left: 19px; + width: 3px; + height: 40px; + background: #fef76c; + transform: rotate(-67deg); + z-index: -1; + animation: frontSofaCoctailDiv1After var(--animation); +} +.front-sofa .coctail > div:nth-child(1):before { + top: 27px; + left: 27px; + width: 50px; + height: 50px; + background: #e16858; + transform: rotate(45deg); + animation: frontSofaCoctailDiv1Before var(--animation); +} +.rabbit-2__body { + top: -59px; + left: 12px; + width: 215px; + height: 215px; + background: #f9edbe; + background: #ece1b4; + border-radius: 50%; + overflow: hidden; + z-index: 1; + animation: rabbit2Body var(--animation); +} +.rabbit-2__body:before { + top: 31px; + left: 128px; + width: 172px; + height: 172px; + background: #f9edbe; + border-radius: 50%; + z-index: 2; + animation: rabbit2BodyBefore var(--animation); +} +.rabbit-2__arm2 { + top: -1px; + left: 83px; + width: 27px; + height: 24px; + background: #fbf0c2; + border-radius: 50% 6px 6px 50%; + transform-origin: left center; + transform: rotate(24deg); + z-index: 2; + animation: rabbit2Arm2 var(--animation); +} +.rabbit-2__arm2:before { + top: 0px; + left: 19px; + width: 42px; + height: 24px; + background-image: linear-gradient(-209deg, #fbf0c2 67%, transparent 67%); + border-radius: 4px 71% 0 4px; + z-index: 1; +} +.rabbit-2__arm2:after { + top: 0px; + left: 19px; + width: 40px; + height: 24px; + background-image: linear-gradient(17deg, #fbf0c2 71%, transparent 71%); + border-radius: 4px 0 76% 4px; +} +.rabbit-2__head { + top: -112px; + left: 24px; + width: 111px; + height: 122px; + background: #ece1b4; + border-radius: 94px 17px 0px 0px / 102px 32px 0 0; + transform-origin: bottom left; + z-index: 1; + animation: rabbit2Head var(--animation); +} +.rabbit-2__head:before { + top: 0px; + left: 80px; + width: 96px; + height: 100px; + border-radius: 10px 74px 10px 0; + background: #ece1b4; +} +.rabbit-2__ear1 { + top: -46px; + left: 86px; + width: 20px; + height: 53px; + background: #eda294; + border-radius: 50% 50% 0 0; + box-shadow: inset 9px 2px #ece0b1; + transform-origin: bottom center; + transform: rotate(13deg); + z-index: -1; + animation: rabbit2Ear1 var(--animation); +} +.rabbit-2__ear1 > div { + bottom: 35px; + right: 1px; + height: 84px; + width: 20px; + background: #eda294; + border-radius: 100% 100% 0 70%; + box-shadow: inset 116px 22px 0 -100px #ece0b1; + transform-origin: 10px 77px; + transform: rotate(31deg); + overflow: hidden; + animation: rabbit2Ear1Div var(--animation); +} +.rabbit-2__ear1 > div:before { + top: 13px; + left: 11px; + width: 20px; + height: 80px; + background: #eda294; + border-radius: 0 0 0 100%; + transform: rotate(13deg); + animation: rabbit2Ear1DivBefore var(--animation); +} +.rabbit-2__ear2 { + top: -38px; + left: 130px; + width: 20px; + height: 64px; + background: #eda294; + border-radius: 50% 10px 0 50%; + box-shadow: inset 9px 5px #ece1b4; + transform-origin: bottom center; + transform: rotate(36deg); + z-index: -1; + animation: rabbit2Ear2 var(--animation); +} +.rabbit-2__ear2:before { + top: -66px; + left: 3px; + width: 20px; + height: 76px; + background: #ece0b1; + border-radius: 100% 100% 0% 100%; + transform-origin: 10px 70px; + transform: rotate(106deg); + animation: rabbit2Ear2Before var(--animation); +} +.rabbit-2__mouth { + top: 71px; + left: 120px; + transform-origin: top center; + transform: rotate(6deg) translate(0px, 0px); + animation: rabbit2Mouth var(--animation); + z-index: 2; +} +.rabbit-2__mouth:before { + top: 0px; + left: 7px; + width: 11px; + height: 7px; + background: #555458; + border-radius: 0 0 3px 3px; + box-shadow: inset 55px 50px 0 -50px #fffff8, inset -55px 50px 0 -50px #fffff8; + z-index: 1; +} +.rabbit-2__mouth:after { + top: -7px; + left: -9px; + width: 41px; + height: 7px; + background: #ebe2b3; + border-radius: 50%; + transform: scale(0.7, 3.5) translate(0px, -2px); + animation: rabbit2MouthAfter var(--animation); +} +.rabbit-2__mouth > div { + top: 0; + left: 0; + width: 22px; + height: 23px; + background: #555458; + border-radius: 4px 4px 10px 12px; + overflow: hidden; + transform: translate(0px, -17px) scaleY(0.8); + animation: rabbit2MouthDiv var(--animation); +} +.rabbit-2__mouth > div:before { + top: 9px; + left: 0px; + width: 15px; + height: 20px; + border-radius: 50%; + background: #e16858; + transform: rotate(0deg) translate(-1px, 1px); +} +.rabbit-2__eye1 { + top: 40px; + left: 104px; + width: 6px; + height: 12px; + background: #555458; + border-radius: 50%; + transform-origin: bottom center; + transform: rotate(5deg); + animation: rabbit2Eye1 var(--animation); + z-index: 2; +} +.rabbit-2__eye1:before { + top: -14px; + left: -3px; + width: 14px; + height: 7px; + border-radius: 50%; + box-shadow: inset -1px 2px 0 0 #555458; + transform: rotate(-32deg); + animation: rabbit2Eye1Before var(--animation); +} +.rabbit-2__eye1:after { + top: -7px; + left: -5px; + width: 14px; + height: 7px; + border-radius: 50%; + background: #ebe2b3; + transform: rotate(-9deg); + animation: rabbit2Eye1After var(--animation); +} +.rabbit-2__eye2 { + top: 44px; + left: 157px; + width: 6px; + height: 12px; + background: #555458; + border-radius: 50%; + transform-origin: bottom center; + transform: rotate(3deg); + animation: rabbit2Eye2 var(--animation); + z-index: 2; +} +.rabbit-2__eye2:before { + top: -18px; + left: -2px; + width: 12px; + height: 12px; + border-radius: 50%; + box-shadow: inset -1px 8px 0 -5px #555458; + transform: rotate(11deg); + animation: rabbit2Eye2Before var(--animation); +} +.rabbit-2__eye2:after { + top: -7px; + left: -5px; + width: 14px; + height: 7px; + border-radius: 50%; + background: #ebe2b3; + transform: rotate(-9deg); + animation: rabbit2Eye2After var(--animation); +} +.rabbit-2__nose { + top: 50px; + left: 127px; + width: 17px; + height: 6px; + background: #ff908c; + border-radius: 50%; + transform: rotate(3deg); + animation: rabbit2Nose var(--animation); + z-index: 2; +} +.rabbit-2__nose:before { + top: -4px; + left: 1px; + width: 14px; + height: 14px; + background-image: linear-gradient(-45deg, #ff908c 50%, transparent 50%); + border-radius: 4px; + transform: rotate(45deg); +} + +@keyframes rabbit2Body { + 2.7% { + transform: translate(0px, 0px) scaleY(1); + } + 3.6% { + transform: translate(1px, 0px) scaleY(1); + } + 4.5% { + transform: translate(1px, 0px) scaleY(1); + } + 5.4% { + transform: translate(3px, 1px) scaleY(1); + } + 8.1% { + transform: translate(3px, 1px) scaleY(1); + } + 9.0% { + transform: translate(2px, -2px) scaleY(1); + } + 16.2% { + transform: translate(2px, -2px) scaleY(1); + } + 17.1% { + transform: translate(3px, 0px) scaleY(1); + } + 18.0% { + transform: translate(3px, 0px) scaleY(1); + } + 18.9% { + transform: translate(2px, 0px) scaleY(1); + } + 19.8% { + transform: translate(2px, 0px) scaleY(1); + } + 20.7% { + transform: translate(0px, 0px) scaleY(1); + } + 21.6% { + transform: translate(0px, 0px) scaleY(1); + } + 22.5% { + transform: translate(-1px, -1px) scaleY(1); + } + 23.4% { + transform: translate(-1px, -1px) scaleY(1); + } + 24.3% { + transform: translate(-4px, -1px) scaleY(1.1); + } + 27.0% { + transform: translate(-4px, -1px) scaleY(1.1); + } + 27.9% { + transform: translate(-5px, -1px) scaleY(1.1); + } + 39.6% { + transform: translate(-5px, -1px) scaleY(1.1); + } + 40.5% { + transform: translate(-2px, 0px) scaleY(1.05); + } + 43.2% { + transform: translate(-2px, 0px) scaleY(1.05); + } + 44.1% { + transform: translate(1px, 0px) scaleY(1); + } + 45.0% { + transform: translate(1px, 0px) scaleY(1); + } + 45.9% { + transform: translate(3px, -2px) scaleY(1); + } + 46.8% { + transform: translate(3px, -2px) scaleY(1); + } + 47.7% { + transform: translate(4px, -3px) scaleY(1); + } + 64.8% { + transform: translate(4px, -3px) scaleY(1); + } + 65.7% { + transform: translate(6px, 0px) scaleY(1); + } + 66.6% { + transform: translate(6px, 0px) scaleY(1); + } + 67.5% { + transform: translate(8px, 4px) scaleY(1); + } + 68.4% { + transform: translate(8px, 4px) scaleY(1); + } + 69.3% { + transform: translate(8px, -3px) scaleY(1.05); + } + 70.2% { + transform: translate(8px, -3px) scaleY(1.05); + } + 71.1% { + transform: translate(8px, -10px) scaleY(1.1); + } + 72.9% { + transform: translate(8px, -10px) scaleY(1.1); + } + 73.8% { + transform: translate(14px, 3px) scaleY(1); + } + 75.6% { + transform: translate(14px, 3px) scaleY(1); + } + 76.5% { + transform: translate(11px, 31px) scaleY(1.3); + } + 78.3% { + transform: translate(11px, 31px) scaleY(1.3); + } + 79.2% { + transform: translate(13px, 5px) scaleY(1); + } + 81.0% { + transform: translate(13px, 5px) scaleY(1); + } + 81.9% { + transform: translate(10px, 20px) scaleY(1.2); + } + 83.7% { + transform: translate(10px, 20px) scaleY(1.2); + } + 84.6% { + transform: translate(15px, 7px) scaleY(1); + } + 83.5% { + transform: translate(15px, 7px) scaleY(1); + } + 84.4% { + transform: translate(25px, 30px) scaleY(1); + } + 85.3% { + transform: translate(25px, 30px) scaleY(1); + } + 86.2% { + transform: translate(30px, 56px) scaleY(1); + } + 87.1% { + transform: translate(30px, 56px) scaleY(1); + } + 90.0% { + transform: translate(36px, 56px) scaleY(0.9); + } + 90.9% { + transform: translate(36px, 56px) scaleY(0.9); + } + 91.8% { + transform: translate(38px, 59px) scaleY(0.9); + } + 100% { + transform: translate(38px, 59px) scaleY(0.9); + } +} +@keyframes rabbit2BodyBefore { + 2.7% { + transform: translate(0px, 0px) scale(1); + } + 3.6% { + transform: translate(1px, 4px) scale(1); + } + 19.8% { + transform: translate(1px, 4px) scale(1); + } + 20.7% { + transform: translate(-6px, 4px) scale(1); + } + 23.4% { + transform: translate(-6px, 4px) scale(1); + } + 24.3% { + transform: translate(-25px, -3px) scale(0.8); + } + 32.4% { + transform: translate(-25px, -3px) scale(0.8); + } + 33.3% { + transform: translate(-25px, -3px) scale(0.8); + } + 39.6% { + transform: translate(-25px, -3px) scale(0.8); + } + 40.5% { + transform: translate(-7px, -1px) scale(1, 0.95); + } + 43.2% { + transform: translate(-7px, -1px) scale(1, 0.95); + } + 44.1% { + transform: translate(-10px, 4px) scale(0.9, 0.95); + } + 66.6% { + transform: translate(-10px, 4px) scale(0.9, 0.95); + } + 67.5% { + transform: translate(-9px, 8px) scale(0.9, 0.95); + } + 68.4% { + transform: translate(-9px, 8px) scale(0.9, 0.95); + } + 69.3% { + transform: translate(-3px, 5px) scale(0.9, 0.95); + } + 75.6% { + transform: translate(-15px, -14px) scale(0.9, 1.2); + } + 76.5% { + transform: translate(-11px, -38px) scale(0.9, 1); + } + 83.7% { + transform: translate(-11px, -38px) scale(0.9, 1); + } + 84.6% { + transform: translate(-17px, -13px) scale(0.9, 1.2); + } + 83.5% { + transform: translate(-17px, -13px) scale(0.9, 1.2); + } + 84.4% { + transform: translate(-17px, -28px) scale(0.9, 1.3); + } + 85.3% { + transform: translate(-17px, -28px) scale(0.9, 1.3); + } + 86.2% { + transform: translate(-17px, -68px) scale(0.9, 1.3); + } + 87.1% { + transform: translate(-17px, -68px) scale(0.9, 1.3); + } + 90.0% { + transform: translate(-23px, -67px) scale(0.9, 1.3); + } + 100% { + transform: translate(-23px, -67px) scale(0.9, 1.3); + } +} +@keyframes rabbit2Head { + 2.7% { + transform: translate(0px, 0px) rotate(0deg); + } + 3.6% { + transform: translate(1px, 2px) rotate(0deg); + } + 4.5% { + transform: translate(1px, 2px) rotate(0deg); + } + 5.4% { + transform: translate(2px, 4px) rotate(0deg); + } + 6.3% { + transform: translate(2px, 4px) rotate(0deg); + } + 7.2% { + transform: translate(3px, 2px) rotate(0deg); + } + 8.1% { + transform: translate(3px, 2px) rotate(0deg); + } + 9.0% { + transform: translate(3px, 1px) rotate(0deg); + } + 16.2% { + transform: translate(3px, 1px) rotate(0deg); + } + 17.1% { + transform: translate(2px, 4px) rotate(0deg); + } + 18.0% { + transform: translate(2px, 4px) rotate(0deg); + } + 18.9% { + transform: translate(1px, 2px) rotate(0deg); + } + 19.8% { + transform: translate(1px, 2px) rotate(0deg); + } + 20.7% { + transform: translate(0px, 1px) rotate(0deg); + } + 21.6% { + transform: translate(0px, 1px) rotate(0deg); + } + 22.5% { + transform: translate(0px, -1px) rotate(0deg); + } + 23.4% { + transform: translate(0px, -1px) rotate(0deg); + } + 24.3% { + transform: translate(-1px, -4px) rotate(0deg); + } + 27.0% { + transform: translate(-1px, -4px) rotate(0deg); + } + 27.9% { + transform: translate(-2px, -7px) rotate(0deg); + } + 28.8% { + transform: translate(-2px, -7px) rotate(0deg); + } + 29.7% { + transform: translate(-2px, -3px) rotate(-2deg); + } + 39.6% { + transform: translate(-2px, -3px) rotate(-2deg); + } + 40.5% { + transform: translate(-1px, -1px) rotate(0deg); + } + 41.4% { + transform: translate(-1px, -1px) rotate(0deg); + } + 42.3% { + transform: translate(0px, 1px) rotate(0deg); + } + 43.2% { + transform: translate(0px, 1px) rotate(0deg); + } + 44.1% { + transform: translate(1px, 2px) rotate(0deg); + } + 45.0% { + transform: translate(1px, 2px) rotate(0deg); + } + 45.9% { + transform: translate(6px, 2px) rotate(0deg); + } + 46.8% { + transform: translate(6px, 2px) rotate(0deg); + } + 47.7% { + transform: translate(6px, -1px) rotate(1deg); + } + 48.6% { + transform: translate(6px, -1px) rotate(1deg); + } + 49.5% { + transform: translate(6px, -2px) rotate(1deg); + } + 64.8% { + transform: translate(6px, -2px) rotate(1deg); + } + 65.7% { + transform: translate(9px, 8px) rotate(0deg); + } + 66.6% { + transform: translate(9px, 8px) rotate(0deg); + } + 67.5% { + transform: translate(9px, 8px) rotate(5deg); + } + 68.4% { + transform: translate(9px, 8px) rotate(5deg); + } + 69.3% { + transform: translate(8px, 8px) rotate(-3deg); + } + 70.2% { + transform: translate(8px, 8px) rotate(-3deg); + } + 71.1% { + transform: translate(29px, 31px) rotate(-30deg); + } + 72.9% { + transform: translate(29px, 31px) rotate(-30deg); + } + 73.8% { + transform: translate(18px, 55px) rotate(-37deg); + } + 75.6% { + transform: translate(18px, 55px) rotate(-37deg); + } + 76.5% { + transform: translate(16px, 42px) rotate(-33deg); + } + 78.3% { + transform: translate(16px, 42px) rotate(-33deg); + } + 79.2% { + transform: translate(16px, 51px) rotate(-33deg); + } + 81.0% { + transform: translate(16px, 51px) rotate(-33deg); + } + 81.9% { + transform: translate(11px, 40px) rotate(-33deg); + } + 83.7% { + transform: translate(11px, 40px) rotate(-33deg); + } + 84.6% { + transform: translate(21px, 58px) rotate(-37deg); + } + 83.5% { + transform: translate(21px, 58px) rotate(-37deg); + } + 84.4% { + transform: translate(24px, 84px) rotate(-37deg); + } + 85.3% { + transform: translate(24px, 84px) rotate(-37deg); + } + 86.2% { + transform: translate(-2px, 86px) rotate(-23deg); + } + 87.1% { + transform: translate(-2px, 86px) rotate(-23deg); + } + 90.0% { + transform: translate(-4px, 91px) rotate(-21deg); + } + 90.9% { + transform: translate(-4px, 91px) rotate(-21deg); + } + 91.8% { + transform: translate(-7px, 91px) rotate(-20deg); + } + 100% { + transform: translate(-7px, 91px) rotate(-20deg); + } +} +@keyframes rabbit2Ear1 { + 4.5% { + transform: rotate(13deg) translate(0px, 0px); + } + 5.4% { + transform: rotate(12deg) translate(0px, 0px); + } + 8.1% { + transform: rotate(12deg) translate(0px, 0px); + } + 9.0% { + transform: rotate(14deg) translate(0px, 0px); + } + 16.2% { + transform: rotate(14deg) translate(0px, 0px); + } + 17.1% { + transform: rotate(12deg) translate(0px, 0px); + } + 18.0% { + transform: rotate(12deg) translate(0px, 0px); + } + 18.9% { + transform: rotate(11deg) translate(0px, 0px); + } + 21.6% { + transform: rotate(11deg) translate(0px, 0px); + } + 22.5% { + transform: rotate(9deg) translate(-5px, 0px); + } + 23.4% { + transform: rotate(9deg) translate(-5px, 0px); + } + 24.3% { + transform: rotate(7deg) translate(-7px, 0px); + } + 25.2% { + transform: rotate(7deg) translate(-7px, 0px); + } + 26.1% { + transform: rotate(7deg) translate(-9px, 0px); + } + 27.0% { + transform: rotate(7deg) translate(-9px, 0px); + } + 27.9% { + transform: rotate(9deg) translate(-9px, 0px); + } + 28.8% { + transform: rotate(9deg) translate(-9px, 0px); + } + 29.7% { + transform: rotate(6deg) translate(-5px, 0px); + } + 30.6% { + transform: rotate(6deg) translate(-5px, 0px); + } + 31.5% { + transform: rotate(5deg) translate(-7px, -1px); + } + 36.0% { + transform: rotate(5deg) translate(-7px, -1px); + } + 36.9% { + transform: rotate(7deg) translate(-7px, -1px); + } + 37.8% { + transform: rotate(7deg) translate(-7px, -1px); + } + 38.7% { + transform: rotate(7deg) translate(-5px, 1px); + } + 39.6% { + transform: rotate(7deg) translate(-5px, 1px); + } + 40.5% { + transform: rotate(5deg) translate(-6px, -3px); + } + 41.4% { + transform: rotate(5deg) translate(-6px, -3px); + } + 42.3% { + transform: rotate(14deg) translate(-4px, -1px); + } + 43.2% { + transform: rotate(14deg) translate(-4px, -1px); + } + 44.1% { + transform: rotate(15deg) translate(0px, -1px); + } + 45.0% { + transform: rotate(15deg) translate(0px, -1px); + } + 45.9% { + transform: rotate(15deg) translate(1px, -1px); + } + 46.8% { + transform: rotate(15deg) translate(1px, -1px); + } + 47.7% { + transform: rotate(14deg) translate(0px, -3px); + } + 64.8% { + transform: rotate(14deg) translate(0px, -3px); + } + 65.7% { + transform: rotate(14deg) translate(1px, 0px); + } + 66.6% { + transform: rotate(14deg) translate(1px, 0px); + } + 67.5% { + transform: rotate(5deg) translate(-5px, 0px); + } + 68.4% { + transform: rotate(5deg) translate(-5px, 0px); + } + 69.3% { + transform: rotate(25deg) translate(-19px, 11px); + } + 70.2% { + transform: rotate(25deg) translate(-19px, 11px); + } + 71.1% { + transform: rotate(55deg) translate(-4px, 11px); + } + 75.6% { + transform: rotate(55deg) translate(-4px, 11px); + } + 76.5% { + transform: rotate(25deg) translate(-10px, 5px); + } + 78.3% { + transform: rotate(25deg) translate(-10px, 5px); + } + 79.2% { + transform: rotate(50deg) translate(-7px, 13px); + } + 81.0% { + transform: rotate(50deg) translate(-7px, 13px); + } + 81.9% { + transform: rotate(25deg) translate(-5px, 3px); + } + 83.7% { + transform: rotate(25deg) translate(-5px, 3px); + } + 84.6% { + transform: rotate(50deg) translate(-3px, 8px); + } + 83.5% { + transform: rotate(50deg) translate(-3px, 8px); + } + 84.4% { + transform: rotate(34deg) translate(-3px, 4px); + } + 85.3% { + transform: rotate(34deg) translate(-3px, 4px); + } + 86.2% { + transform: rotate(11deg) translate(-7px, 0px); + } + 87.1% { + transform: rotate(11deg) translate(-7px, 0px); + } + 90.0% { + transform: rotate(2deg) translate(-9px, 0px); + } + 90.9% { + transform: rotate(2deg) translate(-9px, 0px); + } + 91.8% { + transform: rotate(1deg) translate(-9px, 0px); + } + 100% { + transform: rotate(1deg) translate(-9px, 0px); + } +} +@keyframes rabbit2Ear1Div { + 2.7% { + transform: rotate(31deg) translate(0px, 0px); + } + 3.6% { + transform: rotate(30deg) translate(0px, 0px); + } + 4.5% { + transform: rotate(30deg) translate(0px, 0px); + } + 5.4% { + transform: rotate(29deg) translate(0px, 0px); + } + 8.1% { + transform: rotate(29deg) translate(0px, 0px); + } + 9.0% { + transform: rotate(26deg) translate(0px, 0px); + } + 9.9% { + transform: rotate(26deg) translate(0px, 0px); + } + 10.8% { + transform: rotate(29deg) translate(0px, 0px); + } + 11.7% { + transform: rotate(29deg) translate(0px, 0px); + } + 12.6% { + transform: rotate(31deg) translate(0px, 0px); + } + 13.5% { + transform: rotate(31deg) translate(0px, 0px); + } + 14.4% { + transform: rotate(33deg) translate(0px, 0px); + } + 16.2% { + transform: rotate(33deg) translate(0px, 0px); + } + 17.1% { + transform: rotate(34deg) translate(0px, 0px); + } + 18.0% { + transform: rotate(34deg) translate(0px, 0px); + } + 18.9% { + transform: rotate(38deg) translate(0px, 0px); + } + 19.8% { + transform: rotate(38deg) translate(0px, 0px); + } + 20.7% { + transform: rotate(41deg) translate(0px, 0px); + } + 21.6% { + transform: rotate(41deg) translate(0px, 0px); + } + 22.5% { + transform: rotate(45deg) translate(0px, 0px); + } + 23.4% { + transform: rotate(45deg) translate(0px, 0px); + } + 24.3% { + transform: rotate(49deg) translate(0px, 0px); + } + 25.2% { + transform: rotate(49deg) translate(0px, 0px); + } + 26.1% { + transform: rotate(47deg) translate(0px, 0px); + } + 27.0% { + transform: rotate(47deg) translate(0px, 0px); + } + 27.9% { + transform: rotate(44deg) translate(0px, 0px); + } + 28.8% { + transform: rotate(44deg) translate(0px, 0px); + } + 29.7% { + transform: rotate(45deg) translate(0px, 0px); + } + 30.6% { + transform: rotate(45deg) translate(0px, 0px); + } + 31.5% { + transform: rotate(43deg) translate(0px, 0px); + } + 36.0% { + transform: rotate(43deg) translate(0px, 0px); + } + 36.9% { + transform: rotate(38deg) translate(1px, 2px); + } + 37.8% { + transform: rotate(38deg) translate(1px, 2px); + } + 38.7% { + transform: rotate(33deg) translate(1px, 2px); + } + 39.6% { + transform: rotate(33deg) translate(1px, 2px); + } + 40.5% { + transform: rotate(46deg) translate(0px, -2px); + } + 41.4% { + transform: rotate(46deg) translate(0px, -2px); + } + 42.3% { + transform: rotate(40deg) translate(0px, -1px); + } + 45.0% { + transform: rotate(40deg) translate(0px, -1px); + } + 45.9% { + transform: rotate(43deg) translate(0px, -1px); + } + 46.8% { + transform: rotate(43deg) translate(0px, -1px); + } + 47.7% { + transform: rotate(46deg) translate(0px, -1px); + } + 48.6% { + transform: rotate(46deg) translate(0px, -1px); + } + 49.5% { + transform: rotate(48deg) translate(0px, -1px); + } + 50.4% { + transform: rotate(48deg) translate(0px, -1px); + } + 51.3% { + transform: rotate(50deg) translate(0px, -1px); + } + 52.2% { + transform: rotate(50deg) translate(0px, -1px); + } + 53.1% { + transform: rotate(53deg) translate(-1px, -2px); + } + 64.8% { + transform: rotate(53deg) translate(-1px, -2px); + } + 65.7% { + transform: rotate(40deg) translate(0px, -2px); + } + 66.6% { + transform: rotate(40deg) translate(0px, -2px); + } + 67.5% { + transform: rotate(33deg) translate(0px, -2px); + } + 68.4% { + transform: rotate(33deg) translate(0px, -2px); + } + 69.3% { + transform: rotate(37deg) translate(0px, -2px); + } + 72.9% { + transform: rotate(37deg) translate(0px, -2px); + } + 73.8% { + transform: rotate(44deg) translate(0px, -2px); + } + 75.6% { + transform: rotate(44deg) translate(0px, -2px); + } + 76.5% { + transform: rotate(74deg) translate(-4px, -5px); + } + 78.3% { + transform: rotate(74deg) translate(-4px, -5px); + } + 79.2% { + transform: rotate(50deg) translate(-2px, -5px); + } + 81.0% { + transform: rotate(50deg) translate(-2px, -5px); + } + 81.9% { + transform: rotate(99deg) translate(-6px, -5px); + } + 83.7% { + transform: rotate(99deg) translate(-6px, -5px); + } + 84.6% { + transform: rotate(54deg) translate(-3px, -7px); + } + 85.3% { + transform: rotate(54deg) translate(-3px, -7px); + } + 86.2% { + transform: rotate(64deg) translate(-4px, -7px); + } + 87.1% { + transform: rotate(64deg) translate(-4px, -7px); + } + 90.0% { + transform: rotate(77deg) translate(-5px, -7px); + } + 91.8% { + transform: rotate(77deg) translate(-5px, -7px); + } + 92.7% { + transform: rotate(82deg) translate(-6px, -7px); + } + 100% { + transform: rotate(82deg) translate(-6px, -7px); + } +} +@keyframes rabbit2Ear1DivBefore { + 4.5% { + transform: rotate(13deg); + } + 5.4% { + transform: rotate(14deg); + } + 11.7% { + transform: rotate(14deg); + } + 12.6% { + transform: rotate(13deg); + } + 13.5% { + transform: rotate(13deg); + } + 14.4% { + transform: rotate(12deg); + } + 18.0% { + transform: rotate(12deg); + } + 18.9% { + transform: rotate(11deg); + } + 19.8% { + transform: rotate(11deg); + } + 20.7% { + transform: rotate(10deg); + } + 21.6% { + transform: rotate(10deg); + } + 22.5% { + transform: rotate(8deg); + } + 23.4% { + transform: rotate(8deg); + } + 24.3% { + transform: rotate(6deg); + } + 25.2% { + transform: rotate(6deg); + } + 26.1% { + transform: rotate(8deg); + } + 36.0% { + transform: rotate(8deg); + } + 36.9% { + transform: rotate(10deg); + } + 39.6% { + transform: rotate(10deg); + } + 40.5% { + transform: rotate(11deg); + } + 48.6% { + transform: rotate(11deg); + } + 49.5% { + transform: rotate(9deg); + } + 50.4% { + transform: rotate(9deg); + } + 51.3% { + transform: rotate(8deg); + } + 64.8% { + transform: rotate(8deg); + } + 65.7% { + transform: rotate(12deg); + } + 66.6% { + transform: rotate(12deg); + } + 67.5% { + transform: rotate(14deg); + } + 72.9% { + transform: rotate(14deg); + } + 73.8% { + transform: rotate(11deg); + } + 75.6% { + transform: rotate(11deg); + } + 76.5% { + transform: rotate(3deg); + } + 78.3% { + transform: rotate(3deg); + } + 79.2% { + transform: rotate(10deg); + } + 81.0% { + transform: rotate(10deg); + } + 81.9% { + transform: rotate(1deg); + } + 83.7% { + transform: rotate(1deg); + } + 84.6% { + transform: rotate(10deg); + } + 85.3% { + transform: rotate(10deg); + } + 86.2% { + transform: rotate(8deg); + } + 87.1% { + transform: rotate(8deg); + } + 90.0% { + transform: rotate(4deg); + } + 91.8% { + transform: rotate(4deg); + } + 92.7% { + transform: rotate(3deg); + } + 100% { + transform: rotate(3deg); + } +} +@keyframes rabbit2Ear2 { + 2.7% { + transform: rotate(36deg) translate(0px, 0px); + } + 3.6% { + transform: rotate(34deg) translate(0px, 0px); + } + 16.2% { + transform: rotate(34deg) translate(0px, 0px); + } + 17.1% { + transform: rotate(32deg) translate(0px, 0px); + } + 21.6% { + transform: rotate(32deg) translate(0px, 0px); + } + 22.5% { + transform: rotate(25deg) translate(0px, 0px); + } + 23.4% { + transform: rotate(25deg) translate(0px, 0px); + } + 24.3% { + transform: rotate(14deg) translate(4px, 6px); + } + 25.2% { + transform: rotate(14deg) translate(4px, 6px); + } + 26.1% { + transform: rotate(14deg) translate(3px, 6px); + } + 27.0% { + transform: rotate(14deg) translate(3px, 6px); + } + 27.9% { + transform: rotate(15deg) translate(2px, 7px); + } + 28.8% { + transform: rotate(15deg) translate(2px, 7px); + } + 29.7% { + transform: rotate(15deg) translate(5px, 7px); + } + 30.6% { + transform: rotate(15deg) translate(5px, 7px); + } + 31.5% { + transform: rotate(12deg) translate(5px, 7px); + } + 36.0% { + transform: rotate(12deg) translate(5px, 7px); + } + 36.9% { + transform: rotate(15deg) translate(5px, 8px); + } + 37.8% { + transform: rotate(15deg) translate(5px, 8px); + } + 38.7% { + transform: rotate(16deg) translate(6px, 10px); + } + 39.6% { + transform: rotate(16deg) translate(6px, 10px); + } + 40.5% { + transform: rotate(17deg) translate(2px, 4px); + } + 41.4% { + transform: rotate(17deg) translate(2px, 4px); + } + 42.3% { + transform: rotate(27deg) translate(2px, 4px); + } + 43.2% { + transform: rotate(27deg) translate(2px, 4px); + } + 44.1% { + transform: rotate(29deg) translate(6px, 2px); + } + 45.0% { + transform: rotate(29deg) translate(6px, 2px); + } + 45.9% { + transform: rotate(29deg) translate(8px, 2px); + } + 46.8% { + transform: rotate(29deg) translate(8px, 2px); + } + 47.7% { + transform: rotate(29deg) translate(6px, 1px); + } + 64.8% { + transform: rotate(29deg) translate(6px, 1px); + } + 65.7% { + transform: rotate(29deg) translate(6px, 0px); + } + 66.6% { + transform: rotate(29deg) translate(6px, 0px); + } + 67.5% { + transform: rotate(26deg) translate(-4px, -5px); + } + 68.4% { + transform: rotate(26deg) translate(-4px, -5px); + } + 69.3% { + transform: rotate(47deg) translate(-24px, 11px); + } + 70.2% { + transform: rotate(47deg) translate(-24px, 11px); + } + 71.1% { + transform: rotate(88deg) translate(-16px, 17px); + } + 72.9% { + transform: rotate(88deg) translate(-16px, 17px); + } + 73.8% { + transform: rotate(88deg) translate(-13px, 13px); + } + 75.6% { + transform: rotate(88deg) translate(-13px, 13px); + } + 76.5% { + transform: rotate(40deg) translate(-5px, 7px); + } + 78.3% { + transform: rotate(40deg) translate(-5px, 7px); + } + 79.2% { + transform: rotate(84deg) translate(-21px, 23px); + } + 81.0% { + transform: rotate(84deg) translate(-21px, 23px); + } + 81.9% { + transform: rotate(36deg) translate(2px, 10px); + } + 83.7% { + transform: rotate(36deg) translate(2px, 10px); + } + 84.6% { + transform: rotate(83deg) translate(-10px, 16px); + } + 83.5% { + transform: rotate(83deg) translate(-10px, 16px); + } + 84.4% { + transform: rotate(51deg) translate(-12px, 9px); + } + 85.3% { + transform: rotate(51deg) translate(-12px, 9px); + } + 86.2% { + transform: rotate(17deg) translate(-19px, 4px); + } + 87.1% { + transform: rotate(17deg) translate(-19px, 4px); + } + 90.0% { + transform: rotate(5deg) translate(-19px, 0px); + } + 100% { + transform: rotate(5deg) translate(-19px, 0px); + } +} +@keyframes rabbit2Ear2Before { + 2.7% { + transform: rotate(106deg) translate(0px, 0px); + } + 3.6% { + transform: rotate(107deg) translate(0px, 0px); + } + 6.3% { + transform: rotate(107deg) translate(0px, 0px); + } + 7.2% { + transform: rotate(105deg) translate(0px, 0px); + } + 8.1% { + transform: rotate(105deg) translate(0px, 0px); + } + 9.0% { + transform: rotate(103deg) translate(0px, 0px); + } + 9.9% { + transform: rotate(103deg) translate(0px, 0px); + } + 10.8% { + transform: rotate(102deg) translate(-1px, 1px); + } + 11.7% { + transform: rotate(102deg) translate(-1px, 1px); + } + 12.6% { + transform: rotate(98deg) translate(-1px, 1px); + } + 16.2% { + transform: rotate(98deg) translate(-1px, 1px); + } + 17.1% { + transform: rotate(91deg) translate(-1px, 1px); + } + 18.0% { + transform: rotate(91deg) translate(-1px, 1px); + } + 18.9% { + transform: rotate(99deg) translate(0px, 0px); + } + 19.8% { + transform: rotate(99deg) translate(0px, 0px); + } + 20.7% { + transform: rotate(108deg) translate(0px, 0px); + } + 21.6% { + transform: rotate(108deg) translate(0px, 0px); + } + 22.5% { + transform: rotate(117deg) translate(0px, 0px); + } + 23.4% { + transform: rotate(117deg) translate(0px, 0px); + } + 24.3% { + transform: rotate(123deg) translate(0px, 0px); + } + 25.2% { + transform: rotate(123deg) translate(0px, 0px); + } + 26.1% { + transform: rotate(119deg) translate(0px, 0px); + } + 27.0% { + transform: rotate(119deg) translate(0px, 0px); + } + 27.9% { + transform: rotate(116deg) translate(0px, 0px); + } + 28.8% { + transform: rotate(116deg) translate(0px, 0px); + } + 29.7% { + transform: rotate(113deg) translate(0px, 0px); + } + 30.6% { + transform: rotate(113deg) translate(0px, 0px); + } + 31.5% { + transform: rotate(112deg) translate(0px, 0px); + } + 32.4% { + transform: rotate(112deg) translate(0px, 0px); + } + 33.3% { + transform: rotate(111deg) translate(0px, 0px); + } + 34.2% { + transform: rotate(111deg) translate(0px, 0px); + } + 35.1% { + transform: rotate(109deg) translate(0px, 0px); + } + 36.0% { + transform: rotate(109deg) translate(0px, 0px); + } + 36.9% { + transform: rotate(102deg) translate(0px, 0px); + } + 37.8% { + transform: rotate(102deg) translate(0px, 0px); + } + 38.7% { + transform: rotate(97deg) translate(-1px, 0px); + } + 39.6% { + transform: rotate(97deg) translate(-1px, 0px); + } + 40.5% { + transform: rotate(92deg) translate(-1px, 0px); + } + 41.4% { + transform: rotate(92deg) translate(-1px, 0px); + } + 42.3% { + transform: rotate(82deg) translate(-1px, 0px); + } + 43.2% { + transform: rotate(82deg) translate(-1px, 0px); + } + 44.1% { + transform: rotate(89deg) translate(-1px, 0px); + } + 45.0% { + transform: rotate(89deg) translate(-1px, 0px); + } + 45.9% { + transform: rotate(100deg) translate(-1px, 0px); + } + 46.8% { + transform: rotate(100deg) translate(-1px, 0px); + } + 47.7% { + transform: rotate(105deg) translate(-1px, 0px); + } + 48.6% { + transform: rotate(105deg) translate(-1px, 0px); + } + 49.5% { + transform: rotate(109deg) translate(0px, 0px); + } + 50.4% { + transform: rotate(109deg) translate(0px, 0px); + } + 51.3% { + transform: rotate(112deg) translate(0px, 0px); + } + 52.2% { + transform: rotate(112deg) translate(0px, 0px); + } + 53.1% { + transform: rotate(115deg) translate(0px, 0px); + } + 54.0% { + transform: rotate(115deg) translate(0px, 0px); + } + 54.9% { + transform: rotate(112deg) translate(0px, 0px); + } + 55.8% { + transform: rotate(112deg) translate(0px, 0px); + } + 56.7% { + transform: rotate(111deg) translate(0px, 0px); + } + 64.8% { + transform: rotate(111deg) translate(0px, 0px); + } + 65.7% { + transform: rotate(104deg) translate(0px, 0px); + } + 66.6% { + transform: rotate(104deg) translate(0px, 0px); + } + 67.5% { + transform: rotate(98deg) translate(0px, 1px); + } + 68.4% { + transform: rotate(98deg) translate(0px, 1px); + } + 69.3% { + transform: rotate(81deg) translate(0px, 1px); + } + 70.2% { + transform: rotate(81deg) translate(0px, 1px); + } + 71.1% { + transform: rotate(63deg) translate(-1px, 1px); + } + 72.9% { + transform: rotate(63deg) translate(-1px, 1px); + } + 73.8% { + transform: rotate(46deg) translate(-2px, 4px); + } + 75.6% { + transform: rotate(46deg) translate(-2px, 4px); + } + 76.5% { + transform: rotate(106deg) translate(-2px, 2px); + } + 78.3% { + transform: rotate(106deg) translate(-2px, 2px); + } + 79.2% { + transform: rotate(56deg) translate(-2px, 2px); + } + 81.0% { + transform: rotate(56deg) translate(-2px, 2px); + } + 81.9% { + transform: rotate(109deg) translate(0px, 0px); + } + 83.7% { + transform: rotate(109deg) translate(0px, 0px); + } + 84.6% { + transform: rotate(52deg) translate(-1px, 3px); + } + 83.5% { + transform: rotate(52deg) translate(-1px, 3px); + } + 84.4% { + transform: rotate(50deg) translate(-1px, 3px); + } + 85.3% { + transform: rotate(50deg) translate(-1px, 3px); + } + 86.2% { + transform: rotate(61deg) translate(-1px, 3px); + } + 87.1% { + transform: rotate(61deg) translate(-1px, 3px); + } + 90.0% { + transform: rotate(71deg) translate(-1px, 1px); + } + 90.9% { + transform: rotate(71deg) translate(-1px, 1px); + } + 91.8% { + transform: rotate(74deg) translate(-1px, 1px); + } + 91.8% { + transform: rotate(74deg) translate(-1px, 1px); + } + 92.7% { + transform: rotate(81deg) translate(-1px, 1px); + } + 95.4% { + transform: rotate(81deg) translate(-1px, 1px); + } + 96.3% { + transform: rotate(87deg) translate(-1px, 1px); + } + 97.2% { + transform: rotate(87deg) translate(-1px, 1px); + } + 98.1% { + transform: rotate(91deg) translate(-1px, 1px); + } + 100% { + transform: rotate(91deg) translate(-1px, 1px); + } +} +@keyframes rabbit2Mouth { + 2.7% { + transform: rotate(6deg) translate(0px, 0px); + } + 3.6% { + transform: rotate(8deg) translate(0px, -1px); + } + 4.5% { + transform: rotate(8deg) translate(0px, -1px); + } + 5.4% { + transform: rotate(8deg) translate(0px, -2px); + } + 8.1% { + transform: rotate(8deg) translate(0px, -2px); + } + 9.0% { + transform: rotate(8deg) translate(0px, -5px); + } + 16.2% { + transform: rotate(8deg) translate(0px, -5px); + } + 17.1% { + transform: rotate(8deg) translate(0px, -4px); + } + 18.0% { + transform: rotate(8deg) translate(0px, -4px); + } + 18.9% { + transform: rotate(8deg) translate(-3px, 2px); + } + 19.8% { + transform: rotate(8deg) translate(-3px, 2px); + } + 20.7% { + transform: rotate(11deg) translate(-6px, 5px); + } + 21.6% { + transform: rotate(11deg) translate(-6px, 5px); + } + 22.5% { + transform: rotate(4deg) translate(-11px, 7px); + } + 23.4% { + transform: rotate(4deg) translate(-11px, 7px); + } + 24.3% { + transform: rotate(-2deg) translate(-16px, 11px); + } + 25.2% { + transform: rotate(-2deg) translate(-16px, 11px); + } + 26.1% { + transform: rotate(-2deg) translate(-17px, 7px); + } + 27.0% { + transform: rotate(-2deg) translate(-17px, 7px); + } + 27.9% { + transform: rotate(-2deg) translate(-18px, 8px); + } + 28.8% { + transform: rotate(-2deg) translate(-18px, 8px); + } + 29.7% { + transform: rotate(-2deg) translate(-16px, 8px); + } + 37.8% { + transform: rotate(-2deg) translate(-16px, 8px); + } + 38.7% { + transform: rotate(-2deg) translate(-15px, 9px); + } + 39.6% { + transform: rotate(-2deg) translate(-15px, 9px); + } + 40.5% { + transform: rotate(-3deg) translate(-16px, 7px); + } + 41.4% { + transform: rotate(-3deg) translate(-16px, 7px); + } + 42.3% { + transform: rotate(0deg) translate(-7px, 6px); + } + 43.2% { + transform: rotate(0deg) translate(-7px, 6px); + } + 44.1% { + transform: rotate(14deg) translate(1px, -3px); + } + 45.0% { + transform: rotate(14deg) translate(1px, -3px); + } + 45.9% { + transform: rotate(6deg) translate(0px, -3px); + } + 46.8% { + transform: rotate(6deg) translate(0px, -3px); + } + 47.7% { + transform: rotate(6deg) translate(-1px, -4px); + } + 48.6% { + transform: rotate(6deg) translate(-1px, -4px); + } + 49.5% { + transform: rotate(7deg) translate(-1px, -4px); + } + 64.8% { + transform: rotate(7deg) translate(-1px, -4px); + } + 65.7% { + transform: rotate(4deg) translate(-2px, -7px); + } + 66.6% { + transform: rotate(4deg) translate(-2px, -7px); + } + 67.5% { + transform: rotate(4deg) translate(-6px, -6px); + } + 68.4% { + transform: rotate(4deg) translate(-6px, -6px); + } + 69.3% { + transform: rotate(-14deg) translate(0px, -6px); + } + 70.2% { + transform: rotate(-14deg) translate(0px, -6px); + } + 71.1% { + transform: rotate(7deg) translate(4px, -1px); + } + 72.9% { + transform: rotate(7deg) translate(4px, -1px); + } + 73.8% { + transform: rotate(1deg) translate(9px, 5px); + } + 75.6% { + transform: rotate(1deg) translate(9px, 5px); + } + 76.5% { + transform: rotate(7deg) translate(-5px, 5px); + } + 78.3% { + transform: rotate(7deg) translate(-5px, 5px); + } + 79.2% { + transform: rotate(7deg) translate(-2px, -3px); + } + 81.0% { + transform: rotate(7deg) translate(-2px, -3px); + } + 81.9% { + transform: rotate(10deg) translate(-4px, 2px); + } + 83.7% { + transform: rotate(10deg) translate(-4px, 2px); + } + 84.6% { + transform: rotate(16deg) translate(-3px, -3px); + } + 83.5% { + transform: rotate(16deg) translate(-3px, -3px); + } + 84.4% { + transform: rotate(21deg) translate(-3px, -11px); + } + 85.3% { + transform: rotate(21deg) translate(-3px, -11px); + } + 86.2% { + transform: rotate(21deg) translate(1px, -8px); + } + 87.1% { + transform: rotate(21deg) translate(1px, -8px); + } + 90.0% { + transform: rotate(12deg) translate(6px, -5px); + } + 100% { + transform: rotate(12deg) translate(6px, -5px); + } +} +@keyframes rabbit2MouthAfter { + 83.7% { + transform: scale(0.7, 3.5) translate(0px, -2px); + } + 84.6% { + transform: scale(1.7, 3.5) translate(0px, -2px); + } + 87.1% { + transform: scale(1.7, 3.5) translate(0px, -2px); + } + 90.0% { + transform: scale(1, 3.9) translate(-1px, -2px); + } + 100% { + transform: scale(1, 3.9) translate(-1px, -2px); + } +} +@keyframes rabbit2MouthDiv { + 19.8% { + transform: translate(0px, -17px) scaleY(0.8) scaleX(1); + } + 20.7% { + transform: translate(0px, -12px) scaleY(0.8) scaleX(0.9); + } + 21.6% { + transform: translate(0px, -12px) scaleY(0.8) scaleX(0.9); + } + 22.5% { + transform: translate(0px, -8px) scaleY(0.8) scaleX(0.8); + } + 37.8% { + transform: translate(0px, -8px) scaleY(0.8) scaleX(0.8); + } + 38.7% { + transform: translate(0px, -13px) scaleY(0.8) scaleX(0.8); + } + 39.6% { + transform: translate(0px, -13px) scaleY(0.8) scaleX(0.8); + } + 40.5% { + transform: translate(0px, -17px) scaleY(0.8) scaleX(1); + } + 46.8% { + transform: translate(0px, -17px) scaleY(0.8) scaleX(1); + } + 47.7% { + transform: translate(1px, -11px) scaleY(0.8) scaleX(0.9); + } + 48.6% { + transform: translate(1px, -11px) scaleY(0.8) scaleX(0.9); + } + 49.5% { + transform: translate(1px, -7px) scaleY(0.8) scaleX(0.9); + } + 64.8% { + transform: translate(1px, -7px) scaleY(0.8) scaleX(0.9); + } + 65.7% { + transform: translate(1px, -12px) scaleY(0.8) scaleX(0.9); + } + 66.6% { + transform: translate(1px, -12px) scaleY(0.8) scaleX(0.9); + } + 67.5% { + transform: translate(0px, -17px) scaleY(0.8) scaleX(1.1); + } + 68.4% { + transform: translate(0px, -17px) scaleY(0.8) scaleX(1.1); + } + 69.3% { + transform: translate(0px, 2px) scaleY(1.2) skew(17deg) scaleX(1.4); + } + 70.2% { + transform: translate(0px, 2px) scaleY(1.2) skew(17deg) scaleX(1.4); + } + 71.1% { + transform: translate(2px, 11px) scaleY(2) skew(17deg) scaleX(1.4); + } + 72.9% { + transform: translate(2px, 11px) scaleY(2) skew(17deg) scaleX(1.4); + } + 73.8% { + transform: translate(2px, 7px) scaleY(1.6) skew(17deg) scaleX(1.4); + } + 75.6% { + transform: translate(2px, 7px) scaleY(1.6) skew(17deg) scaleX(1.4); + } + 76.5% { + transform: translate(2px, 10px) scaleY(1.9) skew(17deg) scaleX(1.4); + } + 78.3% { + transform: translate(2px, 10px) scaleY(1.9) skew(17deg) scaleX(1.4); + } + 79.2% { + transform: translate(2px, 6px) scaleY(1.5) skew(17deg) scaleX(1.4); + } + 83.7% { + transform: translate(2px, 6px) scaleY(1.5) skew(17deg) scaleX(1.4); + } + 84.6% { + transform: translate(-1px, -4px) scaleY(1) skew(17deg) scaleX(1.4); + } + 85.3% { + transform: translate(-1px, -4px) scaleY(1) skew(17deg) scaleX(1.4); + } + 86.2% { + transform: translate(-1px, -9px) scaleY(1) skew(17deg) scaleX(1.4); + } + 87.1% { + transform: translate(-1px, -9px) scaleY(1) skew(17deg) scaleX(1.4); + } + 90.0% { + transform: translate(-1px, -19px) scaleY(1) skew(0deg) scaleX(1.7); + } + 100% { + transform: translate(-1px, -19px) scaleY(1) skew(0deg) scaleX(1.7); + } +} +@keyframes rabbit2Eye1 { + 2.7% { + transform: rotate(5deg) translate(0px, 0px); + } + 3.6% { + transform: rotate(5deg) translate(-1px, -1px); + } + 4.5% { + transform: rotate(5deg) translate(-1px, -1px); + } + 5.4% { + transform: rotate(5deg) translate(-1px, -2px); + } + 8.1% { + transform: rotate(5deg) translate(-1px, -2px); + } + 9.0% { + transform: rotate(5deg) translate(0px, -4px); + } + 16.2% { + transform: rotate(5deg) translate(0px, -4px); + } + 17.1% { + transform: rotate(5deg) translate(-1px, -2px); + } + 18.0% { + transform: rotate(5deg) translate(-1px, -2px); + } + 18.9% { + transform: rotate(5deg) translate(-2px, 3px); + } + 19.8% { + transform: rotate(5deg) translate(-2px, 3px); + } + 20.7% { + transform: rotate(5deg) translate(-7px, 4px); + } + 21.6% { + transform: rotate(5deg) translate(-7px, 4px); + } + 22.5% { + transform: rotate(5deg) translate(-13px, 6px); + } + 23.4% { + transform: rotate(5deg) translate(-13px, 6px); + } + 24.3% { + transform: rotate(5deg) translate(-18px, 7px); + } + 25.2% { + transform: rotate(5deg) translate(-18px, 7px); + } + 26.1% { + transform: rotate(5deg) translate(-20px, 5px); + } + 27.0% { + transform: rotate(5deg) translate(-20px, 5px); + } + 27.9% { + transform: rotate(1deg) translate(-22px, 5px); + } + 28.8% { + transform: rotate(1deg) translate(-22px, 5px); + } + 29.7% { + transform: rotate(1deg) translate(-19px, 3px); + } + 30.6% { + transform: rotate(1deg) translate(-19px, 3px); + } + 31.5% { + transform: rotate(1deg) translate(-20px, 2px); + } + 37.8% { + transform: rotate(1deg) translate(-20px, 2px); + } + 38.7% { + transform: rotate(1deg) translate(-19px, 4px); + } + 39.6% { + transform: rotate(1deg) translate(-19px, 4px); + } + 40.5% { + transform: rotate(1deg) translate(-19px, 3px); + } + 41.4% { + transform: rotate(1deg) translate(-19px, 3px); + } + 42.3% { + transform: rotate(1deg) translate(-6px, 4px); + } + 43.2% { + transform: rotate(1deg) translate(-6px, 4px); + } + 44.1% { + transform: rotate(1deg) translate(0px, 0px); + } + 45.0% { + transform: rotate(1deg) translate(0px, 0px); + } + 45.9% { + transform: rotate(4deg) translate(1px, -2px); + } + 46.8% { + transform: rotate(4deg) translate(1px, -2px); + } + 47.7% { + transform: rotate(4deg) translate(0px, -1px); + } + 64.8% { + transform: rotate(4deg) translate(0px, -1px); + } + 65.7% { + transform: rotate(3deg) translate(-2px, -8px); + } + 66.6% { + transform: rotate(3deg) translate(-2px, -8px); + } + 67.5% { + transform: rotate(3deg) translate(-9px, -4px); + } + 68.4% { + transform: rotate(3deg) translate(-9px, -4px); + } + 69.3% { + transform: rotate(-8deg) translate(-16px, -4px); + } + 70.2% { + transform: rotate(-8deg) translate(-16px, -4px); + } + 71.1% { + transform: rotate(11deg) translate(-4px, -5px); + } + 72.9% { + transform: rotate(11deg) translate(-4px, -5px); + } + 73.8% { + transform: rotate(5deg) translate(0px, 2px); + } + 75.6% { + transform: rotate(5deg) translate(0px, 2px); + } + 76.5% { + transform: rotate(5deg) translate(-12px, -8px); + } + 78.3% { + transform: rotate(5deg) translate(-12px, -8px); + } + 79.2% { + transform: rotate(5deg) translate(-7px, -8px); + } + 81.0% { + transform: rotate(5deg) translate(-7px, -8px); + } + 81.9% { + transform: rotate(5deg) translate(-9px, -2px); + } + 83.7% { + transform: rotate(5deg) translate(-9px, -2px); + } + 84.6% { + transform: rotate(5deg) translate(-3px, -2px); + } + 83.5% { + transform: rotate(5deg) translate(-3px, -2px); + } + 84.4% { + transform: rotate(5deg) translate(1px, -10px); + } + 85.3% { + transform: rotate(5deg) translate(1px, -10px); + } + 86.2% { + transform: rotate(18deg) translate(0px, -7px); + } + 87.1% { + transform: rotate(18deg) translate(0px, -7px); + } + 90.0% { + transform: rotate(20deg) translate(1px, -4px); + } + 90.9% { + transform: rotate(20deg) translate(1px, -4px); + } + 91.8% { + transform: rotate(20deg) translate(2px, -3px); + } + 100% { + transform: rotate(20deg) translate(2px, -3px); + } +} +@keyframes rabbit2Eye1Before { + 2.7% { + transform: rotate(-32deg) translate(0px, 0px); + } + 3.6% { + transform: rotate(-36deg) translate(1px, 1px); + } + 4.5% { + transform: rotate(-36deg) translate(1px, 1px); + } + 8.1% { + transform: rotate(-36deg) translate(1px, 1px); + } + 9.0% { + transform: rotate(-36deg) translate(3px, -3px); + } + 9.9% { + transform: rotate(-36deg) translate(3px, -3px); + } + 10.8% { + transform: rotate(-27deg) translate(4px, -6px); + } + 16.2% { + transform: rotate(-27deg) translate(4px, -6px); + } + 17.1% { + transform: rotate(-31deg) translate(1px, 1px); + } + 18.0% { + transform: rotate(-31deg) translate(1px, 1px); + } + 18.9% { + transform: rotate(-31deg) translate(-1px, -3px); + } + 21.6% { + transform: rotate(-31deg) translate(-1px, -3px); + } + 22.5% { + transform: rotate(-31deg) translate(1px, -7px); + } + 23.4% { + transform: rotate(-31deg) translate(1px, -7px); + } + 24.3% { + transform: rotate(-31deg) translate(1px, -9px); + } + 25.2% { + transform: rotate(-31deg) translate(1px, -9px); + } + 26.1% { + transform: rotate(-40deg) translate(4px, -11px); + } + 27.0% { + transform: rotate(-40deg) translate(4px, -11px); + } + 27.9% { + transform: rotate(-40deg) translate(7px, -12px); + } + 28.8% { + transform: rotate(-40deg) translate(7px, -12px); + } + 29.7% { + transform: rotate(-33deg) translate(6px, -11px); + } + 30.6% { + transform: rotate(-33deg) translate(6px, -11px); + } + 31.5% { + transform: rotate(-36deg) translate(7px, -12px); + } + 39.6% { + transform: rotate(-36deg) translate(7px, -12px); + } + 40.5% { + transform: rotate(-32deg) translate(3px, -9px); + } + 41.4% { + transform: rotate(-32deg) translate(3px, -9px); + } + 42.3% { + transform: rotate(-27deg) translate(0px, -5px); + } + 43.2% { + transform: rotate(-27deg) translate(0px, -5px); + } + 44.1% { + transform: rotate(-36deg) translate(1px, 2px); + } + 45.0% { + transform: rotate(-36deg) translate(1px, 2px); + } + 45.9% { + transform: rotate(-36deg) translate(4px, -5px); + } + 46.8% { + transform: rotate(-36deg) translate(4px, -5px); + } + 47.7% { + transform: rotate(-33deg) translate(5px, -6px); + } + 64.8% { + transform: rotate(-33deg) translate(5px, -6px); + } + 65.7% { + transform: rotate(-33deg) translate(5px, -3px); + } + 66.6% { + transform: rotate(-33deg) translate(5px, -3px); + } + 67.5% { + transform: rotate(-38deg) translate(6px, -11px); + } + 68.4% { + transform: rotate(-38deg) translate(6px, -11px); + } + 69.3% { + transform: rotate(-18deg) translate(-1px, -13px); + } + 70.2% { + transform: rotate(-18deg) translate(-1px, -13px); + } + 71.1% { + transform: rotate(-18deg) translate(-1px, -12px); + } + 72.9% { + transform: rotate(-18deg) translate(-1px, -12px); + } + 73.8% { + transform: rotate(-16deg) translate(1px, -16px); + } + 75.6% { + transform: rotate(-16deg) translate(1px, -16px); + } + 76.5% { + transform: rotate(-16deg) translate(0px, -12px); + } + 78.3% { + transform: rotate(-16deg) translate(0px, -12px); + } + 79.2% { + transform: rotate(-14deg) translate(3px, -17px); + } + 83.7% { + transform: rotate(-14deg) translate(3px, -17px); + } + 84.6% { + transform: rotate(-14deg) translate(4px, -23px); + } + 85.3% { + transform: rotate(-14deg) translate(4px, -23px); + } + 86.2% { + transform: rotate(-38deg) translate(-9px, -9px); + } + 87.1% { + transform: rotate(-38deg) translate(-9px, -9px); + } + 90.0% { + transform: rotate(-38deg) translate(-9px, -10px); + } + 100% { + transform: rotate(-38deg) translate(-9px, -10px); + } +} +@keyframes rabbit2Eye1After { + 2.7% { + transform: rotate(-9deg) translate(0px, 0px) scale(1); + box-shadow: none; + } + 3.6% { + transform: rotate(-9deg) translate(1px, 3px) scale(1); + box-shadow: none; + } + 4.5% { + transform: rotate(-9deg) translate(1px, 3px) scale(1); + box-shadow: none; + } + 5.4% { + transform: rotate(13deg) translate(2px, 8px) scale(1, 1.5); + box-shadow: 0 1px; + } + 6.3% { + transform: rotate(13deg) translate(2px, 8px) scale(1, 1.5); + box-shadow: 0 1px; + } + 7.2% { + transform: rotate(13deg) translate(1px, 7px) scale(1, 1.5); + box-shadow: none; + } + 8.1% { + transform: rotate(13deg) translate(1px, 7px) scale(1, 1.5); + box-shadow: none; + } + 9.0% { + transform: rotate(13deg) translate(1px, 0px) scale(1, 1); + box-shadow: none; + } + 16.2% { + transform: rotate(13deg) translate(1px, 0px) scale(1, 1); + box-shadow: none; + } + 17.1% { + transform: rotate(2deg) translate(1px, 8px) scale(1, 1.5); + box-shadow: 0 1px; + } + 18.0% { + transform: rotate(2deg) translate(1px, 8px) scale(1, 1.5); + box-shadow: 0 1px; + } + 18.9% { + transform: rotate(2deg) translate(0px, 5px) scale(1, 1.5); + box-shadow: none; + } + 19.8% { + transform: rotate(2deg) translate(0px, 5px) scale(1, 1.5); + box-shadow: none; + } + 20.7% { + transform: rotate(2deg) translate(0px, -2px) scale(1, 1.5); + box-shadow: none; + } + 43.2% { + transform: rotate(2deg) translate(0px, -2px) scale(1, 1.5); + box-shadow: none; + } + 44.1% { + transform: rotate(11deg) translate(3px, 8px) scale(1, 1.5); + box-shadow: 0 1px; + } + 45.0% { + transform: rotate(11deg) translate(3px, 8px) scale(1, 1.5); + box-shadow: 0 1px; + } + 45.9% { + transform: rotate(11deg) translate(3px, -3px) scale(1, 1.5); + box-shadow: none; + } + 64.8% { + transform: rotate(11deg) translate(3px, -3px) scale(1, 1.5); + box-shadow: none; + } + 65.7% { + transform: rotate(11deg) translate(0px, 2px) scale(1, 1.5); + box-shadow: none; + } + 66.6% { + transform: rotate(11deg) translate(0px, 2px) scale(1, 1.5); + box-shadow: none; + } + 67.5% { + transform: rotate(-1deg) translate(0px, 8px) scale(1, 1.5); + box-shadow: 0 1px; + } + 68.4% { + transform: rotate(-1deg) translate(0px, 8px) scale(1, 1.5); + box-shadow: 0 1px; + } + 69.3% { + transform: rotate(-1deg) translate(0px, -2px) scale(1, 1.5); + box-shadow: none; + } + 83.7% { + transform: rotate(-1deg) translate(0px, -2px) scale(1, 1.5); + box-shadow: none; + } + 84.6% { + transform: rotate(-1deg) translate(0px, 11px) scale(1, 1.5); + box-shadow: 0 -1px; + } + 85.3% { + transform: rotate(-1deg) translate(0px, 11px) scale(1, 1.5); + box-shadow: 0 -1px; + } + 86.2% { + transform: rotate(-1deg) translate(0px, 2px) scale(1, 1.5); + box-shadow: none; + } + 87.1% { + transform: rotate(-1deg) translate(0px, 2px) scale(1, 1.5); + box-shadow: none; + } + 90.0% { + transform: rotate(-1deg) translate(0px, -2px) scale(1, 1.5); + box-shadow: none; + } + 100% { + transform: rotate(-1deg) translate(0px, -2px) scale(1, 1.5); + box-shadow: none; + } +} +@keyframes rabbit2Eye2 { + 2.7% { + transform: rotate(3deg) translate(0px, 0px); + } + 3.6% { + transform: rotate(3deg) translate(-2px, 0px); + } + 4.5% { + transform: rotate(3deg) translate(-2px, 0px); + } + 5.4% { + transform: rotate(3deg) translate(-4px, -2px); + } + 8.1% { + transform: rotate(3deg) translate(-4px, -2px); + } + 9.0% { + transform: rotate(3deg) translate(0px, -1px); + } + 16.2% { + transform: rotate(3deg) translate(0px, -1px); + } + 17.1% { + transform: rotate(3deg) translate(-4px, -2px); + } + 18.0% { + transform: rotate(3deg) translate(-4px, -2px); + } + 18.9% { + transform: rotate(3deg) translate(-5px, 3px); + } + 19.8% { + transform: rotate(3deg) translate(-5px, 3px); + } + 20.7% { + transform: rotate(3deg) translate(-8px, 4px); + } + 21.6% { + transform: rotate(3deg) translate(-8px, 4px); + } + 22.5% { + transform: rotate(3deg) translate(-13px, 4px); + } + 23.4% { + transform: rotate(3deg) translate(-13px, 4px); + } + 24.3% { + transform: rotate(3deg) translate(-16px, 5px); + } + 25.2% { + transform: rotate(3deg) translate(-16px, 5px); + } + 26.1% { + transform: rotate(3deg) translate(-18px, 0px); + } + 27.0% { + transform: rotate(3deg) translate(-18px, 0px); + } + 27.9% { + transform: rotate(-2deg) translate(-18px, 0px); + } + 28.8% { + transform: rotate(-2deg) translate(-18px, 0px); + } + 29.7% { + transform: rotate(-2deg) translate(-16px, 1px); + } + 30.6% { + transform: rotate(-2deg) translate(-16px, 1px); + } + 31.5% { + transform: rotate(-2deg) translate(-16px, -1px); + } + 37.8% { + transform: rotate(-2deg) translate(-16px, -1px); + } + 38.7% { + transform: rotate(-2deg) translate(-15px, 0px); + } + 41.4% { + transform: rotate(-2deg) translate(-15px, 0px); + } + 42.3% { + transform: rotate(7deg) translate(-7px, 3px); + } + 43.2% { + transform: rotate(7deg) translate(-7px, 3px); + } + 44.1% { + transform: rotate(7deg) translate(-3px, 0px); + } + 45.0% { + transform: rotate(7deg) translate(-3px, 0px); + } + 45.9% { + transform: rotate(7deg) translate(-1px, -2px); + } + 46.8% { + transform: rotate(7deg) translate(-1px, -2px); + } + 47.7% { + transform: rotate(7deg) translate(-2px, -2px); + } + 64.8% { + transform: rotate(7deg) translate(-2px, -2px); + } + 65.7% { + transform: rotate(3deg) translate(-3px, -7px); + } + 66.6% { + transform: rotate(3deg) translate(-3px, -7px); + } + 67.5% { + transform: rotate(3deg) translate(-12px, -9px); + } + 68.4% { + transform: rotate(3deg) translate(-12px, -9px); + } + 69.3% { + transform: rotate(-7deg) translate(-13px, -13px); + } + 70.2% { + transform: rotate(-7deg) translate(-13px, -13px); + } + 71.1% { + transform: rotate(13deg) translate(-2px, 5px); + } + 72.9% { + transform: rotate(13deg) translate(-2px, 5px); + } + 73.8% { + transform: rotate(3deg) translate(1px, 4px); + } + 75.6% { + transform: rotate(3deg) translate(1px, 4px); + } + 76.5% { + transform: rotate(7deg) translate(-11px, -2px); + } + 78.3% { + transform: rotate(7deg) translate(-11px, -2px); + } + 79.2% { + transform: rotate(7deg) translate(-8px, -2px); + } + 81.0% { + transform: rotate(7deg) translate(-8px, -2px); + } + 81.9% { + transform: rotate(7deg) translate(-9px, 4px); + } + 83.7% { + transform: rotate(7deg) translate(-9px, 4px); + } + 84.6% { + transform: rotate(7deg) translate(-1px, 14px); + } + 83.5% { + transform: rotate(7deg) translate(-1px, 14px); + } + 84.4% { + transform: rotate(7deg) translate(1px, 10px); + } + 85.3% { + transform: rotate(7deg) translate(1px, 10px); + } + 86.2% { + transform: rotate(14deg) translate(0px, 4px); + } + 87.1% { + transform: rotate(14deg) translate(0px, 4px); + } + 90.0% { + transform: rotate(19deg) translate(2px, 7px); + } + 90.9% { + transform: rotate(19deg) translate(2px, 7px); + } + 91.8% { + transform: rotate(19deg) translate(3px, 7px); + } + 100% { + transform: rotate(19deg) translate(3px, 7px); + } +} +@keyframes rabbit2Eye2Before { + 2.7% { + transform: rotate(11deg) translate(0px, 0px); + } + 3.6% { + transform: rotate(5deg) translate(1px, -1px); + } + 4.5% { + transform: rotate(5deg) translate(1px, -1px); + } + 5.4% { + transform: rotate(5deg) translate(3px, 0px); + } + 8.1% { + transform: rotate(5deg) translate(3px, 0px); + } + 9.0% { + transform: rotate(25deg) translate(-2px, -4px); + } + 9.9% { + transform: rotate(25deg) translate(-2px, -4px); + } + 10.8% { + transform: rotate(30deg) translate(-3px, -6px); + } + 16.2% { + transform: rotate(30deg) translate(-3px, -6px); + } + 17.1% { + transform: rotate(9deg) translate(4px, 0px); + } + 18.0% { + transform: rotate(9deg) translate(4px, 0px); + } + 18.9% { + transform: rotate(6deg) translate(0px, -2px); + } + 19.8% { + transform: rotate(6deg) translate(0px, -2px); + } + 20.7% { + transform: rotate(6deg) translate(-2px, -2px); + } + 21.6% { + transform: rotate(6deg) translate(-2px, -2px); + } + 22.5% { + transform: rotate(6deg) translate(0px, -4px); + } + 23.4% { + transform: rotate(6deg) translate(0px, -4px); + } + 24.3% { + transform: rotate(6deg) translate(0px, -7px); + } + 25.2% { + transform: rotate(6deg) translate(0px, -7px); + } + 26.1% { + transform: rotate(6deg) translate(-1px, -7px); + } + 27.0% { + transform: rotate(6deg) translate(-1px, -7px); + } + 27.9% { + transform: rotate(6deg) translate(0px, -10px); + } + 28.8% { + transform: rotate(6deg) translate(0px, -10px); + } + 29.7% { + transform: rotate(6deg) translate(2px, -10px); + } + 30.6% { + transform: rotate(6deg) translate(2px, -10px); + } + 31.5% { + transform: rotate(6deg) translate(1px, -10px); + } + 39.6% { + transform: rotate(6deg) translate(1px, -10px); + } + 40.5% { + transform: rotate(6deg) translate(2px, -7px); + } + 41.4% { + transform: rotate(6deg) translate(2px, -7px); + } + 42.3% { + transform: rotate(6deg) translate(-5px, -1px); + } + 43.2% { + transform: rotate(6deg) translate(-5px, -1px); + } + 44.1% { + transform: rotate(-4deg) translate(2px, 1px); + } + 45.0% { + transform: rotate(-4deg) translate(2px, 1px); + } + 45.9% { + transform: rotate(19deg) translate(-1px, -4px); + } + 64.8% { + transform: rotate(19deg) translate(-1px, -4px); + } + 65.7% { + transform: rotate(9deg) translate(0px, -2px); + } + 66.6% { + transform: rotate(9deg) translate(0px, -2px); + } + 67.5% { + transform: rotate(31deg) translate(-2px, 0px); + } + 68.4% { + transform: rotate(31deg) translate(-2px, 0px); + } + 69.3% { + transform: rotate(11deg) translate(-13px, -4px); + } + 72.9% { + transform: rotate(11deg) translate(-13px, -4px); + } + 73.8% { + transform: rotate(11deg) translate(-9px, -6px); + } + 75.6% { + transform: rotate(11deg) translate(-9px, -6px); + } + 78.3% { + transform: rotate(11deg) translate(-11px, -3px); + } + 79.2% { + transform: rotate(11deg) translate(-7px, -1px); + } + 83.7% { + transform: rotate(11deg) translate(-7px, -1px); + } + 84.6% { + transform: rotate(20deg) translate(-15px, -11px); + } + 83.5% { + transform: rotate(20deg) translate(-15px, -11px); + } + 84.4% { + transform: rotate(20deg) translate(-15px, -15px); + } + 85.3% { + transform: rotate(20deg) translate(-15px, -15px); + } + 86.2% { + transform: rotate(9deg) translate(-5px, 4px); + } + 87.1% { + transform: rotate(9deg) translate(-5px, 4px); + } + 90.0% { + transform: rotate(2deg) translate(-11px, 0px); + } + 100% { + transform: rotate(2deg) translate(-11px, 0px); + } +} +@keyframes rabbit2Eye2After { + 2.7% { + transform: rotate(-9deg) translate(0px, 0px) scale(1, 1); + box-shadow: none; + } + 3.6% { + transform: rotate(-9deg) translate(1px, 4px) scale(1, 1); + box-shadow: none; + } + 4.5% { + transform: rotate(-9deg) translate(1px, 4px) scale(1, 1); + box-shadow: none; + } + 5.4% { + transform: rotate(12deg) translate(3px, 8px) scale(0.8, 1.45); + box-shadow: 0 1px; + } + 6.3% { + transform: rotate(12deg) translate(3px, 8px) scale(0.8, 1.45); + box-shadow: 0 1px; + } + 7.2% { + transform: rotate(12deg) translate(2px, 7px) scale(0.8, 1.45); + box-shadow: none; + } + 8.1% { + transform: rotate(12deg) translate(2px, 7px) scale(0.8, 1.45); + box-shadow: none; + } + 9.0% { + transform: rotate(12deg) translate(2px, -1px) scale(0.8, 1); + box-shadow: none; + } + 16.2% { + transform: rotate(12deg) translate(2px, -1px) scale(0.8, 1); + box-shadow: none; + } + 17.1% { + transform: rotate(12deg) translate(3px, 8px) scale(0.7, 1.4); + box-shadow: 0 1px; + } + 18.0% { + transform: rotate(12deg) translate(3px, 8px) scale(0.7, 1.4); + box-shadow: 0 1px; + } + 18.9% { + transform: rotate(12deg) translate(1px, 5px) scale(1, 1.4); + box-shadow: none; + } + 19.8% { + transform: rotate(12deg) translate(1px, 5px) scale(1, 1.4); + box-shadow: none; + } + 20.7% { + transform: rotate(12deg) translate(1px, -2px) scale(1, 1.4); + box-shadow: none; + } + 43.2% { + transform: rotate(12deg) translate(1px, -2px) scale(1, 1.4); + box-shadow: none; + } + 44.1% { + transform: rotate(3deg) translate(1px, 8px) scale(0.8, 1.4); + box-shadow: 0 1px; + } + 45.0% { + transform: rotate(3deg) translate(1px, 8px) scale(0.8, 1.4); + box-shadow: 0 1px; + } + 45.9% { + transform: rotate(3deg) translate(-2px, -2px) scale(0.8, 1.4); + box-shadow: none; + } + 64.8% { + transform: rotate(3deg) translate(-2px, -2px) scale(0.8, 1.4); + box-shadow: none; + } + 65.7% { + transform: rotate(3deg) translate(1px, 2px) scale(0.8, 1.4); + box-shadow: none; + } + 66.6% { + transform: rotate(3deg) translate(1px, 2px) scale(0.8, 1.4); + box-shadow: none; + } + 67.5% { + transform: rotate(3deg) translate(2px, 9px) scale(0.8, 1.5); + box-shadow: 0 1px; + } + 68.4% { + transform: rotate(3deg) translate(2px, 9px) scale(0.8, 1.5); + box-shadow: 0 1px; + } + 69.3% { + transform: rotate(3deg) translate(2px, -2px) scale(0.8, 1.5); + box-shadow: none; + } + 83.7% { + transform: rotate(3deg) translate(2px, -2px) scale(0.8, 1.5); + box-shadow: none; + } + 84.6% { + transform: rotate(14deg) translate(3px, 10px) scale(1, 1.5); + box-shadow: 0 -1px; + } + 85.3% { + transform: rotate(14deg) translate(3px, 10px) scale(1, 1.5); + box-shadow: 0 -1px; + } + 86.2% { + transform: rotate(14deg) translate(1px, 3px) scale(1, 1.5); + box-shadow: none; + } + 87.1% { + transform: rotate(14deg) translate(1px, 3px) scale(1, 1.5); + box-shadow: none; + } + 90.0% { + transform: rotate(14deg) translate(-1px, -2px) scale(1, 1.5); + box-shadow: none; + } + 100% { + transform: rotate(14deg) translate(-1px, -2px) scale(1, 1.5); + box-shadow: none; + } +} +@keyframes rabbit2Nose { + 2.7% { + transform: rotate(3deg) translate(0, 0); + } + 3.6% { + transform: rotate(3deg) translate(0px, -1px); + } + 4.5% { + transform: rotate(3deg) translate(0px, -1px); + } + 5.4% { + transform: rotate(4deg) translate(0px, -2px); + } + 18.0% { + transform: rotate(4deg) translate(0px, -2px); + } + 18.9% { + transform: rotate(4deg) translate(-2px, 2px); + } + 19.8% { + transform: rotate(4deg) translate(-2px, 2px); + } + 20.7% { + transform: rotate(4deg) translate(-6px, 5px); + } + 21.6% { + transform: rotate(4deg) translate(-6px, 5px); + } + 22.5% { + transform: rotate(1deg) translate(-12px, 5px); + } + 23.4% { + transform: rotate(1deg) translate(-12px, 5px); + } + 24.3% { + transform: rotate(1deg) translate(-18px, 9px); + } + 25.2% { + transform: rotate(1deg) translate(-18px, 9px); + } + 26.1% { + transform: rotate(1deg) translate(-20px, 6px); + } + 27.0% { + transform: rotate(1deg) translate(-20px, 6px); + } + 27.9% { + transform: rotate(0deg) translate(-21px, 6px); + } + 28.8% { + transform: rotate(0deg) translate(-21px, 6px); + } + 29.7% { + transform: rotate(-2deg) translate(-19px, 6px); + } + 30.6% { + transform: rotate(-2deg) translate(-19px, 6px); + } + 31.5% { + transform: rotate(-2deg) translate(-19px, 5px); + } + 37.8% { + transform: rotate(-2deg) translate(-19px, 5px); + } + 38.7% { + transform: rotate(-1deg) translate(-18px, 6px); + } + 41.4% { + transform: rotate(-1deg) translate(-18px, 6px); + } + 42.3% { + transform: rotate(1deg) translate(-6px, 4px); + } + 43.2% { + transform: rotate(1deg) translate(-6px, 4px); + } + 44.1% { + transform: rotate(1deg) translate(1px, 0px); + } + 64.8% { + transform: rotate(1deg) translate(1px, 0px); + } + 65.7% { + transform: rotate(1deg) translate(-1px, -8px); + } + 66.6% { + transform: rotate(1deg) translate(-1px, -8px); + } + 67.5% { + transform: rotate(1deg) translate(-7px, -7px); + } + 68.4% { + transform: rotate(1deg) translate(-7px, -7px); + } + 69.3% { + transform: rotate(-11deg) translate(-7px, -4px); + } + 70.2% { + transform: rotate(-11deg) translate(-7px, -4px); + } + 71.1% { + transform: rotate(9deg) translate(4px, 6px); + } + 72.9% { + transform: rotate(9deg) translate(4px, 6px); + } + 73.8% { + transform: rotate(9deg) translate(8px, 6px); + } + 75.6% { + transform: rotate(9deg) translate(8px, 6px); + } + 76.5% { + transform: rotate(16deg) translate(-6px, 8px); + } + 78.3% { + transform: rotate(16deg) translate(-6px, 8px); + } + 79.2% { + transform: rotate(11deg) translate(-1px, 2px); + } + 81.0% { + transform: rotate(11deg) translate(-1px, 2px); + } + 81.9% { + transform: rotate(14deg) translate(-2px, 8px); + } + 83.7% { + transform: rotate(14deg) translate(-2px, 8px); + } + 84.6% { + transform: rotate(14deg) translate(-1px, 3px); + } + 83.5% { + transform: rotate(14deg) translate(-1px, 3px); + } + 84.4% { + transform: rotate(14deg) translate(3px, -2px); + } + 85.3% { + transform: rotate(14deg) translate(3px, -2px); + } + 86.2% { + transform: rotate(17deg) translate(4px, 2px); + } + 87.1% { + transform: rotate(17deg) translate(4px, 2px); + } + 90.0% { + transform: rotate(17deg) translate(6px, 2px); + } + 90.9% { + transform: rotate(17deg) translate(6px, 2px); + } + 91.8% { + transform: rotate(17deg) translate(7px, 2px); + } + 100% { + transform: rotate(17deg) translate(7px, 2px); + } +} +@keyframes rabbit2Arm2 { + 4.5% { + transform: rotate(24deg) translate(0, 0); + } + 5.4% { + transform: rotate(24deg) translate(3px, 0px); + } + 6.3% { + transform: rotate(24deg) translate(3px, 0px); + } + 7.2% { + transform: rotate(24deg) translate(4px, -1px); + } + 8.1% { + transform: rotate(24deg) translate(4px, -1px); + } + 9.0% { + transform: rotate(22deg) translate(5px, -1px); + } + 16.2% { + transform: rotate(22deg) translate(5px, -1px); + } + 17.1% { + transform: rotate(22deg) translate(5px, 1px); + } + 18.0% { + transform: rotate(22deg) translate(5px, 1px); + } + 18.9% { + transform: rotate(23deg) translate(7px, -2px); + } + 19.8% { + transform: rotate(23deg) translate(7px, -2px); + } + 20.7% { + transform: rotate(24deg) translate(8px, -5px); + } + 21.6% { + transform: rotate(24deg) translate(8px, -5px); + } + 22.5% { + transform: rotate(17deg) translate(12px, -5px); + } + 23.4% { + transform: rotate(17deg) translate(12px, -5px); + } + 24.3% { + transform: rotate(11deg) translate(17px, -5px); + } + 25.2% { + transform: rotate(11deg) translate(17px, -5px); + } + 26.1% { + transform: rotate(6deg) translate(19px, -6px); + } + 27.0% { + transform: rotate(6deg) translate(19px, -6px); + } + 27.9% { + transform: rotate(2deg) translate(21px, -8px); + } + 28.8% { + transform: rotate(2deg) translate(21px, -8px); + } + 29.7% { + transform: rotate(0deg) translate(22px, -9px); + } + 30.6% { + transform: rotate(0deg) translate(22px, -9px); + } + 31.5% { + transform: rotate(0deg) translate(23px, -12px); + } + 32.4% { + transform: rotate(0deg) translate(23px, -12px); + } + 33.3% { + transform: rotate(-1deg) translate(22px, -12px); + } + 34.2% { + transform: rotate(-1deg) translate(22px, -12px); + } + 35.1% { + transform: rotate(-1deg) translate(22px, -11px); + } + 36.0% { + transform: rotate(-1deg) translate(22px, -11px); + } + 36.9% { + transform: rotate(1deg) translate(23px, -8px); + } + 37.8% { + transform: rotate(1deg) translate(23px, -8px); + } + 38.7% { + transform: rotate(4deg) translate(22px, -8px); + } + 39.6% { + transform: rotate(4deg) translate(22px, -8px); + } + 40.5% { + transform: rotate(14deg) translate(17px, -6px); + } + 41.4% { + transform: rotate(14deg) translate(17px, -6px); + } + 42.3% { + transform: rotate(21deg) translate(10px, -2px); + } + 43.2% { + transform: rotate(21deg) translate(10px, -2px); + } + 44.1% { + transform: rotate(23deg) translate(7px, -2px); + } + 45.0% { + transform: rotate(23deg) translate(7px, -2px); + } + 45.9% { + transform: rotate(23deg) translate(5px, -2px); + } + 46.8% { + transform: rotate(23deg) translate(5px, -2px); + } + 47.7% { + transform: rotate(23deg) translate(6px, -2px); + } + 64.8% { + transform: rotate(23deg) translate(6px, -2px); + } + 65.7% { + transform: rotate(22deg) translate(5px, -1px); + } + 68.4% { + transform: rotate(22deg) translate(5px, -1px); + } + 69.3% { + transform: rotate(35deg) translate(9px, 1px); + } + 70.2% { + transform: rotate(35deg) translate(9px, 1px); + } + 71.1% { + transform: rotate(49deg) translate(14px, -3px); + } + 72.9% { + transform: rotate(49deg) translate(14px, -3px); + } + 73.8% { + transform: rotate(45deg) translate(16px, 0px); + } + 75.6% { + transform: rotate(45deg) translate(16px, 0px); + } + 76.5% { + transform: rotate(49deg) translate(16px, 0px); + } + 78.3% { + transform: rotate(49deg) translate(16px, 0px); + } + 79.2% { + transform: rotate(55deg) translate(17px, 0px); + } + 81.0% { + transform: rotate(55deg) translate(17px, 0px); + } + 81.9% { + transform: rotate(47deg) translate(17px, 0px); + } + 83.7% { + transform: rotate(47deg) translate(17px, 0px); + } + 84.6% { + transform: rotate(57deg) translate(17px, -4px); + } + 83.5% { + transform: rotate(57deg) translate(17px, -4px); + } + 84.4% { + transform: rotate(48deg) translate(44px, 14px); + } + 85.3% { + transform: rotate(48deg) translate(44px, 14px); + } + 86.2% { + transform: rotate(80deg) translate(61px, -19px); + } + 87.1% { + transform: rotate(80deg) translate(61px, -19px); + } + 90.0% { + transform: rotate(80deg) translate(65px, -16px); + } + 100% { + transform: rotate(80deg) translate(65px, -16px); + } +} +@keyframes frontSofaCoctailBefore { + 23.4% { + transform: rotate(-20deg) translate(0, 0); + } + 24.3% { + transform: rotate(-43deg) translate(-4px, -2px); + } + 27.0% { + transform: rotate(-43deg) translate(-4px, -2px); + } + 27.9% { + transform: rotate(-53deg) translate(-5px, -8px); + } + 30.6% { + transform: rotate(-53deg) translate(-5px, -8px); + } + 31.5% { + transform: rotate(-63deg) translate(-6px, -11px); + } + 39.6% { + transform: rotate(-63deg) translate(-6px, -11px); + } + 40.5% { + transform: rotate(-45deg) translate(-5px, -7px); + } + 41.4% { + transform: rotate(-45deg) translate(-5px, -7px); + } + 42.3% { + transform: rotate(-15deg) translate(1px, -2px); + } + 68.4% { + transform: rotate(-15deg) translate(1px, -2px); + } + 69.3% { + transform: rotate(-65deg) translate(7px, -10px); + } + 70.2% { + transform: rotate(-65deg) translate(7px, -10px); + } + 71.1% { + transform: rotate(-93deg) translate(9px, -6px); + } + 72.9% { + transform: rotate(-93deg) translate(9px, -6px); + } + 73.8% { + transform: rotate(-113deg) translate(13px, 0px); + } + 75.6% { + transform: rotate(-113deg) translate(13px, 0px); + } + 76.5% { + transform: rotate(-163deg) translate(2px, 9px); + } + 78.3% { + transform: rotate(-163deg) translate(2px, 9px); + } + 79.2% { + transform: rotate(-203deg) translate(-4px, -6px); + } + 81.0% { + transform: rotate(-203deg) translate(-4px, -6px); + } + 81.9% { + transform: rotate(-133deg) translate(-21px, 6px); + } + 100% { + transform: rotate(-133deg) translate(-21px, 6px); + } +} +@keyframes frontSofaCoctailDiv1After { + 23.4% { + transform: rotate(-67deg) translate(0, 0); + } + 24.3% { + transform: rotate(-83deg) translate(-4px, -1px); + } + 27.0% { + transform: rotate(-83deg) translate(-4px, -1px); + } + 27.9% { + transform: rotate(-97deg) translate(-4px, 0px); + } + 30.6% { + transform: rotate(-97deg) translate(-4px, 0px); + } + 31.5% { + transform: rotate(-98deg) translate(-7px, -0.9px); + } + 39.6% { + transform: rotate(-98deg) translate(-7px, -0.9px); + } + 40.5% { + transform: rotate(-87deg) translate(-5px, -0.9px); + } + 41.4% { + transform: rotate(-87deg) translate(-5px, -0.9px); + } + 42.3% { + transform: rotate(-71deg) translate(1px, 4px); + } + 68.4% { + transform: rotate(-71deg) translate(1px, 4px); + } + 69.3% { + transform: rotate(-101deg) translate(6px, 3px); + } + 70.2% { + transform: rotate(-101deg) translate(6px, 3px); + } + 71.1% { + transform: rotate(-140deg) translate(11px, 3px); + } + 72.9% { + transform: rotate(-140deg) translate(11px, 3px); + } + 73.8% { + transform: rotate(-176deg) translate(13px, 3px); + } + 75.6% { + transform: rotate(-176deg) translate(13px, 3px); + } + 76.5% { + transform: rotate(-216deg) translate(1px, 11px); + } + 78.3% { + transform: rotate(-216deg) translate(1px, 11px); + } + 79.2% { + transform: rotate(-65deg) translate(4px, -4px); + } + 81.0% { + transform: rotate(-65deg) translate(4px, -4px); + } + 81.9% { + transform: rotate(-5deg) translate(21px, -8px); + } + 100% { + transform: rotate(-5deg) translate(21px, -8px); + } +} +@keyframes frontSofaCoctailDiv1Before { + 21.6% { + transform: rotate(45deg) translate(0, 0); + } + 22.5% { + transform: rotate(49deg) translate(0, 0); + } + 23.4% { + transform: rotate(49deg) translate(0, 0); + } + 24.3% { + transform: rotate(52deg) translate(0, 0); + } + 27.0% { + transform: rotate(52deg) translate(0, 0); + } + 27.9% { + transform: rotate(52deg) translate(2px, 0px); + } + 28.8% { + transform: rotate(52deg) translate(2px, 0px); + } + 29.7% { + transform: rotate(52deg) translate(3px, 0px); + } + 30.6% { + transform: rotate(52deg) translate(3px, 0px); + } + 31.5% { + transform: rotate(61deg) translate(9px, 0px); + } + 32.4% { + transform: rotate(61deg) translate(9px, 0px); + } + 33.3% { + transform: rotate(61deg) translate(18px, 0px); + } + 100% { + transform: rotate(61deg) translate(18px, 0px); + } +} + +@keyframes rabbit1Body { + 2.7% { + transform: translate(0, 0) scale(1, 1) rotate(0deg); + } + 3.6% { + transform: translate(2px, 1px) scale(1, 1) rotate(0deg); + } + 4.5% { + transform: translate(2px, 1px) scale(1, 1) rotate(0deg); + } + 5.4% { + transform: translate(1px, 1px) scale(0.99, 1) rotate(0deg); + } + 27.0% { + transform: translate(1px, 1px) scale(0.99, 1) rotate(0deg); + } + 27.9% { + transform: translate(-2px, 0px) scale(1, 1) rotate(0deg); + } + 28.8% { + transform: translate(-2px, 0px) scale(1, 1) rotate(0deg); + } + 29.7% { + transform: translate(-1px, 0px) scale(1, 1) rotate(0deg); + } + 30.6% { + transform: translate(-1px, 0px) scale(1, 1) rotate(0deg); + } + 31.5% { + transform: translate(-5px, 1px) scale(1, 1) rotate(0deg); + } + 32.4% { + transform: translate(-5px, 1px) scale(1, 1) rotate(0deg); + } + 33.3% { + transform: translate(-7px, 0px) scale(1, 1) rotate(0deg); + } + 37.8% { + transform: translate(-7px, 0px) scale(1, 1) rotate(0deg); + } + 38.7% { + transform: translate(-9px, 0px) scale(1, 1) rotate(0deg); + } + 49.5% { + transform: translate(-9px, 0px) scale(1, 1) rotate(0deg); + } + 50.4% { + transform: translate(-3px, 0px) scale(1, 1) rotate(0deg); + } + 51.3% { + transform: translate(-3px, 0px) scale(1, 1) rotate(0deg); + } + 52.2% { + transform: translate(5px, 1px) scale(1, 1) rotate(0deg); + } + 53.1% { + transform: translate(5px, 1px) scale(1, 1) rotate(0deg); + } + 54.0% { + transform: translate(-3px, 1px) scale(1, 1) rotate(0deg); + } + 54.9% { + transform: translate(-3px, 1px) scale(1, 1) rotate(0deg); + } + 55.8% { + transform: translate(-5px, -8px) scale(0.95, 1.1) rotate(0deg); + } + 56.7% { + transform: translate(-5px, -8px) scale(0.95, 1.1) rotate(0deg); + } + 57.6% { + transform: translate(-6px, 1px) scale(0.95, 1) rotate(0deg); + } + 72.0% { + transform: translate(-6px, 1px) scale(0.95, 1) rotate(0deg); + } + 72.9% { + transform: translate(0px, 1px) scale(1, 1) rotate(0deg); + } + 73.8% { + transform: translate(0px, 1px) scale(1, 1) rotate(0deg); + } + 74.7% { + transform: translate(2px, 1px) scale(1, 1) rotate(0deg); + } + 75.6% { + transform: translate(7px, -3px) scale(0.95, 1) rotate(5deg); + } + 76.5% { + transform: translate(7px, -3px) scale(0.95, 1) rotate(5deg); + } + 77.4% { + transform: translate(6px, 0px) scale(0.97, 1) rotate(7deg); + } + 87.3% { + transform: translate(6px, 0px) scale(0.97, 1) rotate(7deg); + } + 88.2% { + transform: translate(7px, -8px) scale(0.97, 1) rotate(12deg); + } + 81.0% { + transform: translate(7px, -8px) scale(0.97, 1) rotate(12deg); + } + 81.9% { + transform: translate(4px, 0px) scale(0.95, 1) rotate(18deg); + } + 82.8% { + transform: translate(4px, 0px) scale(0.95, 1) rotate(18deg); + } + 84.6% { + transform: translate(4px, 0px) scale(0.95, 1) rotate(20deg); + } + 85.5% { + transform: translate(4px, 0px) scale(0.95, 1) rotate(20deg); + } + 87.3% { + transform: translate(7px, -10px) scale(1, 1) rotate(0deg); + } + 90.9% { + transform: translate(7px, -10px) scale(1, 1) rotate(0deg); + } + 92.7% { + transform: rotate(5deg) translate(3px, 4px) scale(1, 1); + } + 95.4% { + transform: rotate(5deg) translate(3px, 4px) scale(1, 1); + } + 96.3% { + transform: translate(6px, -10px) scale(1, 1) rotate(0deg); + } + 100% { + transform: translate(6px, -10px) scale(1, 1) rotate(0deg); + } +} +@keyframes rabbit1BodyBefore { + 27.0% { + transform: rotate(11deg) translate(0, 0) scale(1, 1); + } + 27.9% { + transform: rotate(5deg) translate(8px, 3px) scale(1, 1); + } + 28.8% { + transform: rotate(5deg) translate(8px, 3px) scale(1, 1); + } + 29.7% { + transform: rotate(-11deg) translate(12px, 5px) scale(1, 1.2); + } + 30.6% { + transform: rotate(-11deg) translate(12px, 5px) scale(1, 1.2); + } + 31.5% { + transform: rotate(-18deg) translate(20px, 8px) scale(1, 1.2); + } + 32.4% { + transform: rotate(-18deg) translate(20px, 8px) scale(1, 1.2); + } + 33.3% { + transform: rotate(-30deg) translate(28px, 8px) scale(1, 1.2); + } + 49.5% { + transform: rotate(-30deg) translate(28px, 8px) scale(1, 1.2); + } + 50.4% { + transform: rotate(-37deg) translate(35px, 8px) scale(1, 1.2); + } + 51.3% { + transform: rotate(-37deg) translate(35px, 8px) scale(1, 1.2); + } + 52.2% { + transform: rotate(-46deg) translate(44px, 8px) scale(1, 1.2); + } + 53.1% { + transform: rotate(-46deg) translate(44px, 8px) scale(1, 1.2); + } + 54.0% { + transform: rotate(-29deg) translate(34px, -8px) scale(1, 1.2); + } + 56.7% { + transform: rotate(-29deg) translate(34px, -8px) scale(1, 1.2); + } + 57.6% { + transform: rotate(-15deg) translate(16px, 1px) scale(1, 1.2); + } + 72.0% { + transform: rotate(-15deg) translate(16px, 1px) scale(1, 1.2); + } + 72.9% { + transform: rotate(-31deg) translate(28px, -12px) scale(1.1, 1.1); + } + 73.8% { + transform: rotate(-31deg) translate(28px, -12px) scale(1.1, 1.1); + } + 87.3% { + transform: rotate(0deg) translate(5px, 14px) scale(1, 1.4); + } + 88.2% { + transform: rotate(-7deg) translate(16px, -14px) scale(1, 1); + } + 85.5% { + transform: rotate(-7deg) translate(16px, -14px) scale(1, 1); + } + 87.3% { + transform: rotate(10deg) translate(0px, 1px) scale(1, 1); + } + 100% { + transform: rotate(10deg) translate(0px, 1px) scale(1, 1); + } +} +@keyframes rabbit1BodyAfter { + 25.2% { + transform: translate(0, 0) scale(1, 1); + } + 26.1% { + transform: translate(-5px, 0px) scale(1, 0.9); + } + 27.0% { + transform: translate(-5px, 0px) scale(1, 0.9); + } + 27.9% { + transform: translate(-6px, 5px) scale(1, 0.9); + } + 28.8% { + transform: translate(-6px, 5px) scale(1, 0.9); + } + 29.7% { + transform: translate(-12px, -2px) scale(1, 1); + } + 30.6% { + transform: translate(-12px, -2px) scale(1, 1); + } + 31.5% { + transform: translate(-14px, -2px) scale(1, 1); + } + 32.4% { + transform: translate(-14px, -2px) scale(1, 1); + } + 33.3% { + transform: translate(-21px, -2px) scale(1, 1); + } + 49.5% { + transform: translate(-21px, -2px) scale(1, 1); + } + 50.4% { + transform: translate(-30px, 4px) scale(1, 0.9); + } + 51.3% { + transform: translate(-30px, 4px) scale(1, 0.9); + } + 52.2% { + transform: translate(-41px, 7px) scale(1, 0.85); + } + 53.1% { + transform: translate(-41px, 7px) scale(1, 0.85); + } + 54.0% { + transform: translate(-30px, 1px) scale(1, 0.9); + } + 54.9% { + transform: translate(-30px, 1px) scale(1, 0.9); + } + 55.8% { + transform: translate(-31px, -7px) scale(1, 1); + } + 56.7% { + transform: translate(-31px, -7px) scale(1, 1); + } + 57.6% { + transform: translate(-30px, -6px) scale(1, 0.95); + } + 70.2% { + transform: translate(-30px, -6px) scale(1, 0.95); + } + 71.1% { + transform: translate(-29px, -2px) scale(1, 0.95); + } + 73.8% { + transform: translate(-29px, -2px) scale(1, 0.95); + } + 87.3% { + transform: translate(-12px, -7px) scale(0.9, 1); + } + 88.2% { + transform: translate(-16px, 15px) scale(0.9, 0.9); + } + 100% { + transform: translate(-16px, 15px) scale(0.9, 0.9); + } +} +@keyframes rabbit1Head { + 4.5% { + transform: translate(0, 0) rotate(0deg) scale(1, 1); + } + 5.4% { + transform: translate(0px, -1px) rotate(0deg) scale(1, 1); + } + 6.3% { + transform: translate(0px, -1px) rotate(0deg) scale(1, 1); + } + 7.2% { + transform: translate(-1px, -1px) rotate(0deg) scale(1, 1); + } + 11.7% { + transform: translate(-1px, -1px) rotate(0deg) scale(1, 1); + } + 12.6% { + transform: translate(0px, -1px) rotate(0deg) scale(1, 1); + } + 25.2% { + transform: translate(0px, -1px) rotate(0deg) scale(1, 1); + } + 26.1% { + transform: translate(-7px, 2px) rotate(0deg) scale(1, 1); + } + 27.0% { + transform: translate(-7px, 2px) rotate(0deg) scale(1, 1); + } + 27.9% { + transform: translate(-17px, 5px) rotate(0deg) scale(1, 1); + } + 28.8% { + transform: translate(-17px, 5px) rotate(0deg) scale(1, 1); + } + 29.7% { + transform: translate(-47px, -6px) rotate(-4deg) scale(1, 1); + } + 30.6% { + transform: translate(-47px, -6px) rotate(-4deg) scale(1, 1); + } + 31.5% { + transform: translate(-59px, -1px) rotate(-4deg) scale(1, 1); + } + 32.4% { + transform: translate(-59px, -1px) rotate(-4deg) scale(1, 1); + } + 33.3% { + transform: translate(-74px, 3px) rotate(-4deg) scale(1, 1); + } + 34.2% { + transform: translate(-74px, 3px) rotate(-4deg) scale(1, 1); + } + 35.1% { + transform: translate(-77px, 4px) rotate(-4deg) scale(1, 1); + } + 36.0% { + transform: translate(-77px, 4px) rotate(-4deg) scale(1, 1); + } + 36.9% { + transform: translate(-78px, 5px) rotate(-4deg) scale(1, 1); + } + 49.5% { + transform: translate(-78px, 5px) rotate(-4deg) scale(1, 1); + } + 50.4% { + transform: translate(-80px, 13px) rotate(-4deg) scale(1, 1); + } + 51.3% { + transform: translate(-80px, 13px) rotate(-4deg) scale(1, 1); + } + 52.2% { + transform: translate(-80px, 23px) rotate(-4deg) scale(1, 1); + } + 53.1% { + transform: translate(-80px, 23px) rotate(-4deg) scale(1, 1); + } + 54.0% { + transform: translate(-65px, -1px) rotate(-4deg) scale(1, 1); + } + 54.9% { + transform: translate(-65px, -1px) rotate(-4deg) scale(1, 1); + } + 55.8% { + transform: translate(-57px, -22px) rotate(-4deg) scale(0.95, 1.1); + } + 56.7% { + transform: translate(-57px, -22px) rotate(-4deg) scale(0.95, 1.1); + } + 57.6% { + transform: translate(-55px, -17px) rotate(-4deg) scale(1, 1.1); + } + 70.2% { + transform: translate(-55px, -17px) rotate(-4deg) scale(1, 1.1); + } + 71.1% { + transform: translate(-56px, -13px) rotate(-4deg) scale(1, 1.1); + } + 72.0% { + transform: translate(-56px, -13px) rotate(-4deg) scale(1, 1.1); + } + 72.9% { + transform: translate(-65px, 5px) rotate(-5deg) scale(1, 1); + } + 73.8% { + transform: translate(-65px, 5px) rotate(-5deg) scale(1, 1); + } + 74.7% { + transform: translate(-8px, -35px) rotate(5deg) scale(1, 1); + } + 75.6% { + transform: translate(2px, -41px) rotate(5deg) scale(0.95, 1); + } + 76.5% { + transform: translate(2px, -41px) rotate(5deg) scale(0.95, 1); + } + 77.4% { + transform: translate(2px, -22px) rotate(5deg) scale(0.95, 1); + } + 87.3% { + transform: translate(2px, -22px) rotate(5deg) scale(0.95, 1); + } + 88.2% { + transform: translate(4px, -3px) rotate(9deg) scale(1, 1); + } + 80.1% { + transform: translate(4px, -3px) rotate(9deg) scale(1, 1); + } + 81.0% { + transform: translate(8px, -4px) rotate(7deg) scale(0.97, 1); + } + 81.9% { + transform: translate(12px, -6px) rotate(8deg) scale(0.97, 1); + } + 82.8% { + transform: translate(12px, -6px) rotate(8deg) scale(0.97, 1); + } + 84.6% { + transform: translate(6px, 4px) rotate(8deg) scale(1, 1); + } + 85.5% { + transform: translate(6px, 4px) rotate(8deg) scale(1, 1); + } + 87.3% { + transform: translate(11px, -6px) rotate(8deg) scale(0.97, 1); + } + 88.2% { + transform: translate(11px, -6px) rotate(8deg) scale(0.97, 1); + } + 90.0% { + transform: translate(4px, 7px) rotate(3deg) scale(1, 1); + } + 90.9% { + transform: translate(4px, 7px) rotate(3deg) scale(1, 1); + } + 92.7% { + transform: translate(-2px, 2px) rotate(0deg) scale(1, 1); + } + 95.4% { + transform: translate(-2px, 2px) rotate(0deg) scale(1, 1); + } + 96.3% { + transform: translate(-5px, 2px) rotate(0deg) scale(1, 1); + } + 100% { + transform: translate(-5px, 2px) rotate(0deg) scale(1, 1); + } +} +@keyframes rabbit1HeadBefore { + 73.8% { + transform: rotate(10deg) scale(0, 0) translate(0, 0); + } + 76.5% { + transform: rotate(10deg) scale(1, 1) translate(0, 0); + } + 77.4% { + transform: rotate(71deg) scale(1, 1) translate(4px, 10px); + } + 87.3% { + transform: rotate(71deg) scale(1, 1) translate(4px, 10px); + } + 88.2% { + transform: rotate(71deg) scale(0, 0) translate(4px, 10px); + } + 81.0% { + transform: rotate(71deg) scale(0, 0) translate(4px, 10px); + } + 81.9% { + transform: rotate(71deg) scale(1, 1) translate(-1px, 12px); + } + 82.8% { + transform: rotate(71deg) scale(1, 1) translate(-1px, 12px); + } + 84.6% { + transform: rotate(71deg) scale(0, 0) translate(-1px, 12px); + } + 100% { + transform: rotate(71deg) scale(0, 0) translate(-1px, 12px); + } +} +@keyframes rabbit1HeadAfter { + 56.7% { + transform: rotate(48deg); + } + 57.6% { + transform: rotate(58deg); + } + 100% { + transform: rotate(58deg); + } +} +@keyframes rabbit1BackLeg { + 25.2% { + transform: rotate(-8deg) translate(0, 0); + } + 26.1% { + transform: rotate(-12deg) translate(-1px, -4px); + } + 27.0% { + transform: rotate(-12deg) translate(-1px, -4px); + } + 27.9% { + transform: rotate(-11deg) translate(2px, -2px); + } + 28.8% { + transform: rotate(-11deg) translate(2px, -2px); + } + 29.7% { + transform: rotate(-4deg) translate(3px, 0px); + } + 30.6% { + transform: rotate(-4deg) translate(3px, 0px); + } + 31.5% { + transform: rotate(4deg) translate(7px, 4px); + } + 32.4% { + transform: rotate(4deg) translate(7px, 4px); + } + 33.3% { + transform: rotate(1deg) translate(7px, 3px); + } + 34.2% { + transform: rotate(1deg) translate(7px, 3px); + } + 35.1% { + transform: rotate(-9deg) translate(5px, -3px); + } + 36.0% { + transform: rotate(-9deg) translate(5px, -3px); + } + 36.9% { + transform: rotate(-17deg) translate(-3px, -16px); + } + 37.8% { + transform: rotate(-17deg) translate(-3px, -16px); + } + 38.7% { + transform: rotate(-20deg) translate(-7px, -21px); + } + 49.5% { + transform: rotate(-20deg) translate(-7px, -21px); + } + 50.4% { + transform: rotate(-17deg) translate(-3px, -16px); + } + 51.3% { + transform: rotate(-17deg) translate(-3px, -16px); + } + 52.2% { + transform: rotate(3deg) translate(9px, 6px); + } + 53.1% { + transform: rotate(3deg) translate(9px, 6px); + } + 54.0% { + transform: rotate(0deg) translate(8px, 6px); + } + 54.9% { + transform: rotate(0deg) translate(8px, 6px); + } + 55.8% { + transform: rotate(-7deg) translate(3px, -7px); + } + 70.2% { + transform: rotate(-7deg) translate(3px, -7px); + } + 71.1% { + transform: rotate(-3deg) translate(7px, -7px); + } + 72.0% { + transform: rotate(-3deg) translate(7px, -7px); + } + 72.9% { + transform: rotate(13deg) translate(20px, 5px); + } + 73.8% { + transform: rotate(13deg) translate(20px, 5px); + } + 74.7% { + transform: rotate(-11deg) translate(6px, 6px); + } + 75.6% { + transform: rotate(-25deg) translate(-9px, -1px); + } + 76.5% { + transform: rotate(-25deg) translate(-9px, -1px); + } + 77.4% { + transform: rotate(-22deg) translate(-8px, -5px); + } + 87.3% { + transform: rotate(-22deg) translate(-8px, -5px); + } + 88.2% { + transform: rotate(-8deg) translate(-2px, -1px); + } + 80.1% { + transform: rotate(-8deg) translate(-2px, -1px); + } + 81.0% { + transform: rotate(-15deg) translate(-5px, -3px); + } + 82.8% { + transform: rotate(-15deg) translate(-5px, -3px); + } + 84.6% { + transform: rotate(-8deg) translate(0px, 0px); + } + 85.5% { + transform: rotate(-8deg) translate(0px, 0px); + } + 87.3% { + transform: rotate(-19deg) translate(-8px, -4px); + } + 88.2% { + transform: rotate(-19deg) translate(-8px, -4px); + } + 90.0% { + transform: rotate(-8deg) translate(-2px, 0px); + } + 90.9% { + transform: rotate(-8deg) translate(-2px, 0px); + } + 92.7% { + transform: rotate(-16deg) translate(-6px, -2px); + } + 93.6% { + transform: rotate(-16deg) translate(-6px, -2px); + } + 94.5% { + transform: rotate(-20deg) translate(-6px, -2px); + } + 95.4% { + transform: rotate(-20deg) translate(-6px, -2px); + } + 96.3% { + transform: rotate(-20deg) translate(-3px, 1px); + } + 100% { + transform: rotate(-20deg) translate(-3px, 1px); + } +} +@keyframes rabbit1BackLegAfter { + 25.2% { + transform: rotate(-18deg); + } + 26.1% { + transform: rotate(-20deg); + } + 27.0% { + transform: rotate(-20deg); + } + 27.9% { + transform: rotate(-24deg); + } + 28.8% { + transform: rotate(-24deg); + } + 29.7% { + transform: rotate(-19deg); + } + 30.6% { + transform: rotate(-19deg); + } + 31.5% { + transform: rotate(-18deg); + } + 32.4% { + transform: rotate(-18deg); + } + 33.3% { + transform: rotate(-20deg); + } + 37.8% { + transform: rotate(-20deg); + } + 38.7% { + transform: rotate(-24deg); + } + 39.6% { + transform: rotate(-24deg); + } + 40.5% { + transform: rotate(-20deg); + } + 49.5% { + transform: rotate(-20deg); + } + 50.4% { + transform: rotate(-15deg); + } + 53.1% { + transform: rotate(-15deg); + } + 54.0% { + transform: rotate(-20deg); + } + 70.2% { + transform: rotate(-20deg); + } + 71.1% { + transform: rotate(-13deg); + } + 87.3% { + transform: rotate(-13deg); + } + 88.2% { + transform: rotate(-15deg); + } + 100% { + transform: rotate(-15deg); + } +} +@keyframes rabbit1Leg { + 25.2% { + transform: rotate(-27deg) translate(0, 0) scale(1, 1); + } + 26.1% { + transform: rotate(-23deg) translate(-3px, 3px) scale(1, 1); + } + 27.0% { + transform: rotate(-23deg) translate(-3px, 3px) scale(1, 1); + } + 27.9% { + transform: rotate(-23deg) translate(-3px, 2px) scale(1, 1); + } + 28.8% { + transform: rotate(-23deg) translate(-3px, 2px) scale(1, 1); + } + 29.7% { + transform: rotate(-14deg) translate(-7px, 10px) scale(1, 1); + } + 30.6% { + transform: rotate(-14deg) translate(-7px, 10px) scale(1, 1); + } + 31.5% { + transform: rotate(-14deg) translate(-6px, 10px) scale(1, 1); + } + 36.0% { + transform: rotate(-14deg) translate(-6px, 10px) scale(1, 1); + } + 36.9% { + transform: rotate(-15deg) translate(-5px, 9px) scale(1, 1); + } + 39.6% { + transform: rotate(-15deg) translate(-5px, 9px) scale(1, 1); + } + 40.5% { + transform: rotate(-15deg) translate(-8px, 9px) scale(1, 1); + } + 51.3% { + transform: rotate(-15deg) translate(-8px, 9px) scale(1, 1); + } + 52.2% { + transform: rotate(-13deg) translate(-12px, 12px) scale(1, 1); + } + 53.1% { + transform: rotate(-13deg) translate(-12px, 12px) scale(1, 1); + } + 54.0% { + transform: rotate(-13deg) translate(-10px, 12px) scale(1, 1); + } + 72.0% { + transform: rotate(-13deg) translate(-10px, 12px) scale(1, 1); + } + 72.9% { + transform: rotate(-16deg) translate(-23px, 9px) scale(0.9, 1.2); + } + 73.8% { + transform: rotate(-16deg) translate(-23px, 9px) scale(0.9, 1.2); + } + 74.7% { + transform: rotate(-27deg) translate(1px, 3px) scale(0.9, 1); + } + 75.6% { + transform: rotate(-38deg) translate(14px, -1px) scale(0.9, 1); + } + 76.5% { + transform: rotate(-38deg) translate(14px, -1px) scale(0.9, 1); + } + 77.4% { + transform: rotate(-32deg) translate(1px, -2px) scale(0.9, 1); + } + 87.3% { + transform: rotate(-32deg) translate(1px, -2px) scale(0.9, 1); + } + 88.2% { + transform: rotate(-26deg) translate(-14px, -2px) scale(0.9, 1); + } + 80.1% { + transform: rotate(-26deg) translate(-14px, -2px) scale(0.9, 1); + } + 81.0% { + transform: rotate(-30deg) translate(-8px, -1px) scale(0.9, 1); + } + 82.8% { + transform: rotate(-30deg) translate(-8px, -1px) scale(0.9, 1); + } + 84.6% { + transform: rotate(-21deg) translate(-12px, 5px) scale(0.9, 1); + } + 85.5% { + transform: rotate(-21deg) translate(-12px, 5px) scale(0.9, 1); + } + 87.3% { + transform: rotate(-31deg) translate(-3px, -1px) scale(0.9, 1); + } + 88.2% { + transform: rotate(-31deg) translate(-3px, -1px) scale(0.9, 1); + } + 90.0% { + transform: rotate(-28deg) translate(-10px, -1px) scale(0.9, 1); + } + 93.6% { + transform: rotate(-28deg) translate(-10px, -1px) scale(0.9, 1); + } + 94.5% { + transform: rotate(-25deg) translate(-10px, 5px) scale(0.9, 1); + } + 100% { + transform: rotate(-25deg) translate(-10px, 5px) scale(0.9, 1); + } +} +@keyframes rabbit1LegBefore { + 28.8% { + transform: rotate(-9deg) translate(0, 0); + } + 29.7% { + transform: rotate(2deg) translate(-3px, 0px); + } + 30.6% { + transform: rotate(2deg) translate(-3px, 0px); + } + 31.5% { + transform: rotate(-10deg) translate(0px, 0px); + } + 32.4% { + transform: rotate(-10deg) translate(0px, 0px); + } + 33.3% { + transform: rotate(-23deg) translate(2px, 0px); + } + 36.0% { + transform: rotate(-23deg) translate(2px, 0px); + } + 36.9% { + transform: rotate(-29deg) translate(3px, 3px); + } + 39.6% { + transform: rotate(-29deg) translate(3px, 3px); + } + 40.5% { + transform: rotate(-23deg) translate(2px, 3px); + } + 49.5% { + transform: rotate(-23deg) translate(2px, 3px); + } + 50.4% { + transform: rotate(-19deg) translate(1px, 3px); + } + 51.3% { + transform: rotate(-19deg) translate(1px, 3px); + } + 52.2% { + transform: rotate(-10deg) translate(-1px, -1px); + } + 81.0% { + transform: rotate(-10deg) translate(-1px, -1px); + } + 81.9% { + transform: rotate(-21deg) translate(1px, -1px); + } + 88.2% { + transform: rotate(-21deg) translate(1px, -1px); + } + 90.0% { + transform: rotate(-16deg) translate(1px, -1px); + } + 100% { + transform: rotate(-16deg) translate(1px, -1px); + } +} +@keyframes rabbit1Arm1 { + 4.5% { + transform: rotate(-11deg) translate(0, 0); + } + 5.4% { + transform: rotate(-1deg) translate(2px, -1px); + } + 6.3% { + transform: rotate(-1deg) translate(2px, -1px); + } + 7.2% { + transform: rotate(18deg) translate(18px, -1px); + } + 8.1% { + transform: rotate(18deg) translate(18px, -1px); + } + 9.0% { + transform: rotate(24deg) translate(18px, -1px); + } + 9.9% { + transform: rotate(24deg) translate(18px, -1px); + } + 10.8% { + transform: rotate(39deg) translate(8px, -5px); + } + 11.7% { + transform: rotate(39deg) translate(8px, -5px); + } + 12.6% { + transform: rotate(52deg) translate(15px, -3px); + } + 13.5% { + transform: rotate(52deg) translate(15px, -3px); + } + 14.4% { + transform: rotate(67deg) translate(18px, 0px); + } + 15.3% { + transform: rotate(67deg) translate(18px, 0px); + } + 16.2% { + transform: rotate(73deg) translate(30px, 3px); + } + 17.1% { + transform: rotate(73deg) translate(30px, 3px); + } + 18.0% { + transform: rotate(53deg) translate(33px, 7px); + } + 25.2% { + transform: rotate(53deg) translate(33px, 7px); + } + 26.1% { + transform: rotate(41deg) translate(29px, 13px); + } + 27.0% { + transform: rotate(41deg) translate(29px, 13px); + } + 27.9% { + transform: rotate(31deg) translate(27px, 22px); + } + 28.8% { + transform: rotate(31deg) translate(27px, 22px); + } + 29.7% { + transform: rotate(50deg) translate(17px, 38px); + } + 30.6% { + transform: rotate(50deg) translate(17px, 38px); + } + 31.5% { + transform: rotate(17deg) translate(-16px, 32px); + } + 32.4% { + transform: rotate(17deg) translate(-16px, 32px); + } + 33.3% { + transform: rotate(7deg) translate(-34px, 33px); + } + 34.2% { + transform: rotate(7deg) translate(-34px, 33px); + } + 35.1% { + transform: rotate(-15deg) translate(-48px, 9px); + } + 36.0% { + transform: rotate(-15deg) translate(-48px, 9px); + } + 36.9% { + transform: rotate(-39deg) translate(-54px, -24px); + } + 37.8% { + transform: rotate(-39deg) translate(-54px, -24px); + } + 38.7% { + transform: rotate(-37deg) translate(-55px, -23px); + } + 39.6% { + transform: rotate(-37deg) translate(-55px, -23px); + } + 40.5% { + transform: rotate(-14deg) translate(-56px, 5px); + } + 41.4% { + transform: rotate(-14deg) translate(-56px, 5px); + } + 42.3% { + transform: rotate(-4deg) translate(-53px, 18px); + } + 44.1% { + transform: rotate(-4deg) translate(-53px, 18px); + } + 45.0% { + transform: rotate(0deg) translate(-53px, 22px); + } + 49.5% { + transform: rotate(0deg) translate(-53px, 22px); + } + 50.4% { + transform: rotate(5deg) translate(-44px, 28px); + } + 51.3% { + transform: rotate(5deg) translate(-44px, 28px); + } + 52.2% { + transform: rotate(5deg) translate(-37px, 27px); + } + 53.1% { + transform: rotate(5deg) translate(-37px, 27px); + } + 54.0% { + transform: rotate(35deg) translate(1px, 49px); + } + 54.9% { + transform: rotate(35deg) translate(1px, 49px); + } + 55.8% { + transform: rotate(35deg) translate(-1px, 43px); + } + 56.7% { + transform: rotate(35deg) translate(-1px, 43px); + } + 57.6% { + transform: rotate(65deg) translate(28px, 54px); + } + 72.0% { + transform: rotate(65deg) translate(28px, 54px); + } + 72.9% { + transform: rotate(-13deg) translate(-25px, -20px); + } + 73.8% { + transform: rotate(-13deg) translate(-25px, -20px); + } + 74.7% { + transform: rotate(-14deg) translate(-22px, -12px); + } + 75.6% { + transform: rotate(-6deg) translate(-16px, 0px); + } + 76.5% { + transform: rotate(-6deg) translate(-16px, 0px); + } + 77.4% { + transform: rotate(-21deg) translate(-8px, -7px); + } + 87.3% { + transform: rotate(-21deg) translate(-8px, -7px); + } + 88.2% { + transform: rotate(-31deg) translate(-2px, -6px); + } + 80.1% { + transform: rotate(-31deg) translate(-2px, -6px); + } + 81.0% { + transform: rotate(-31deg) translate(8px, -6px); + } + 81.9% { + transform: rotate(-21deg) translate(10px, 0px); + } + 90.9% { + transform: rotate(-21deg) translate(10px, 0px); + } + 92.7% { + transform: rotate(-21deg) translate(-5px, 0px); + } + 93.6% { + transform: rotate(-21deg) translate(-5px, 0px); + } + 94.5% { + transform: rotate(-21deg) translate(-9px, -2px); + } + 100% { + transform: rotate(-21deg) translate(-9px, -2px); + } +} +@keyframes rabbit1Arm1Before { + 4.5% { + transform: rotate(8deg) translate(0, 0) scale(1, 1); + border-radius: 100% 2px 2px 40%; + } + 5.4% { + transform: rotate(-5deg) translate(-3px, -1px) scale(0.8, 0.8); + border-radius: 100% 2px 2px 40%; + } + 6.3% { + transform: rotate(-5deg) translate(-3px, -1px) scale(0.8, 0.8); + border-radius: 100% 2px 2px 40%; + } + 7.2% { + transform: rotate(-33deg) translate(-11px, -8px) scale(0.8, 0.8); + border-radius: 100% 2px 2px 40%; + } + 8.1% { + transform: rotate(-33deg) translate(-11px, -8px) scale(0.8, 0.8); + border-radius: 100% 2px 2px 40%; + } + 9.0% { + transform: rotate(-21deg) translate(-14px, -5px) scale(0.5, 0.8); + border-radius: 100% 2px 2px 40%; + } + 9.9% { + transform: rotate(-21deg) translate(-14px, -5px) scale(0.5, 0.8); + border-radius: 100% 2px 2px 40%; + } + 10.8% { + transform: rotate(9deg) translate(-3px, 2px) scale(0.5, 0.8); + border-radius: 100% 2px 2px 40%; + } + 11.7% { + transform: rotate(9deg) translate(-3px, 2px) scale(0.5, 0.8); + border-radius: 100% 2px 2px 40%; + } + 12.6% { + transform: rotate(11deg) translate(-10px, 2px) scale(0.5, 0.8); + border-radius: 40% 2px 2px 100%; + } + 13.5% { + transform: rotate(11deg) translate(-10px, 2px) scale(0.5, 0.8); + border-radius: 40% 2px 2px 100%; + } + 14.4% { + transform: rotate(11deg) translate(-10px, 3px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 15.3% { + transform: rotate(11deg) translate(-10px, 3px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 16.2% { + transform: rotate(27deg) translate(-10px, 6px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 17.1% { + transform: rotate(27deg) translate(-10px, 6px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 18.0% { + transform: rotate(27deg) translate(-8px, 7px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 28.8% { + transform: rotate(27deg) translate(-8px, 7px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 29.7% { + transform: rotate(7deg) translate(-9px, 3px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 30.6% { + transform: rotate(7deg) translate(-9px, 3px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 31.5% { + transform: rotate(7deg) translate(2px, 3px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 53.1% { + transform: rotate(7deg) translate(2px, 3px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 54.0% { + transform: rotate(62deg) translate(-6px, 14px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 56.7% { + transform: rotate(62deg) translate(-6px, 14px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 57.6% { + transform: rotate(56deg) translate(-9px, 14px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 72.0% { + transform: rotate(56deg) translate(-9px, 14px) scale(0.5, 0.9); + border-radius: 40% 2px 2px 100%; + } + 72.9% { + transform: rotate(-40deg) translate(-15px, -9px) scale(0.5, 0.9); + border-radius: 100% 2px 2px 40%; + } + 73.8% { + transform: rotate(-40deg) translate(-15px, -9px) scale(0.5, 0.9); + border-radius: 100% 2px 2px 40%; + } + 74.7% { + transform: rotate(5deg) translate(-1px, 1px) scale(0.5, 0.9); + border-radius: 100% 2px 2px 40%; + } + 100% { + transform: rotate(5deg) translate(-1px, 1px) scale(0.5, 0.9); + border-radius: 100% 2px 2px 40%; + } +} +@keyframes rabbit1Arm2 { + 25.2% { + transform: translate(0, 0) rotate(0deg); + } + 26.1% { + transform: translate(-13px, 5px) rotate(22deg); + } + 27.0% { + transform: translate(-13px, 5px) rotate(22deg); + } + 27.9% { + transform: translate(-26px, 4px) rotate(22deg); + } + 28.8% { + transform: translate(-26px, 4px) rotate(22deg); + } + 29.7% { + transform: translate(-44px, 3px) rotate(15deg); + } + 30.6% { + transform: translate(-44px, 3px) rotate(15deg); + } + 31.5% { + transform: translate(-64px, 3px) rotate(26deg); + } + 32.4% { + transform: translate(-64px, 3px) rotate(26deg); + } + 33.3% { + transform: translate(-88px, 7px) rotate(50deg); + } + 34.2% { + transform: translate(-88px, 7px) rotate(50deg); + } + 35.1% { + transform: translate(-87px, 12px) rotate(50deg); + } + 36.0% { + transform: translate(-87px, 12px) rotate(50deg); + } + 36.9% { + transform: translate(-87px, 15px) rotate(70deg); + } + 37.8% { + transform: translate(-87px, 15px) rotate(70deg); + } + 38.7% { + transform: translate(-85px, 18px) rotate(70deg); + } + 49.5% { + transform: translate(-85px, 18px) rotate(70deg); + } + 50.4% { + transform: translate(-86px, 19px) rotate(49deg); + } + 51.3% { + transform: translate(-86px, 19px) rotate(49deg); + } + 52.2% { + transform: translate(-83px, 22px) rotate(12deg); + } + 53.1% { + transform: translate(-83px, 22px) rotate(12deg); + } + 54.0% { + transform: translate(-89px, 14px) rotate(122deg); + } + 54.9% { + transform: translate(-89px, 14px) rotate(122deg); + } + 55.8% { + transform: translate(-92px, 12px) rotate(152deg); + } + 73.8% { + transform: translate(-92px, 12px) rotate(152deg); + } + 74.7% { + transform: translate(-52px, 19px) rotate(112deg); + } + 75.6% { + transform: translate(-42px, 24px) rotate(72deg); + } + 76.5% { + transform: translate(-42px, 24px) rotate(72deg); + } + 77.4% { + transform: translate(-34px, 25px) rotate(60deg); + } + 87.3% { + transform: translate(-34px, 25px) rotate(60deg); + } + 88.2% { + transform: translate(-25px, 27px) rotate(50deg); + } + 80.1% { + transform: translate(-25px, 27px) rotate(50deg); + } + 81.0% { + transform: translate(-30px, 28px) rotate(80deg); + } + 81.9% { + transform: translate(-33px, 28px) rotate(91deg); + } + 82.8% { + transform: translate(-33px, 28px) rotate(91deg); + } + 84.6% { + transform: translate(-24px, 26px) rotate(41deg); + } + 85.5% { + transform: translate(-24px, 26px) rotate(41deg); + } + 87.3% { + transform: translate(-31px, 28px) rotate(75deg); + } + 88.2% { + transform: translate(-31px, 28px) rotate(75deg); + } + 90.0% { + transform: translate(-31px, 28px) rotate(74deg); + } + 93.6% { + transform: translate(-31px, 28px) rotate(74deg); + } + 94.5% { + transform: translate(-30px, 37px) rotate(84deg); + } + 100% { + transform: translate(-30px, 37px) rotate(84deg); + } +} +@keyframes rabbit1Arm2Before { + 25.2% { + transform: rotate(10deg) translate(0, 0); + } + 26.1% { + transform: rotate(-50deg) translate(-7px, -13px); + } + 27.0% { + transform: rotate(-50deg) translate(-7px, -13px); + } + 27.9% { + transform: rotate(-98deg) translate(-5px, -27px); + } + 28.8% { + transform: rotate(-98deg) translate(-5px, -27px); + } + 29.7% { + transform: rotate(-102deg) translate(-4px, -24px); + } + 30.6% { + transform: rotate(-102deg) translate(-4px, -24px); + } + 31.5% { + transform: rotate(-109deg) translate(-4px, -22px); + } + 34.2% { + transform: rotate(-109deg) translate(-4px, -22px); + } + 35.1% { + transform: rotate(-87deg) translate(-4px, -19px); + } + 49.5% { + transform: rotate(-87deg) translate(-4px, -19px); + } + 50.4% { + transform: rotate(-81deg) translate(-2px, -12px); + } + 51.3% { + transform: rotate(-81deg) translate(-2px, -12px); + } + 52.2% { + transform: rotate(-64deg) translate(-4px, -12px); + } + 53.1% { + transform: rotate(-64deg) translate(-4px, -12px); + } + 54.0% { + transform: rotate(-159deg) translate(-1px, -18px); + } + 54.9% { + transform: rotate(-159deg) translate(-1px, -18px); + } + 55.8% { + transform: rotate(-88deg) translate(-2px, -18px); + } + 74.7% { + transform: rotate(-88deg) translate(-2px, -18px); + } + 75.6% { + transform: rotate(-118deg) translate(-9px, -20px); + } + 87.3% { + transform: rotate(-118deg) translate(-9px, -20px); + } + 88.2% { + transform: rotate(-68deg) translate(-9px, -16px); + } + 80.1% { + transform: rotate(-68deg) translate(-9px, -16px); + } + 81.0% { + transform: rotate(-128deg) translate(-6px, -16px); + } + 82.8% { + transform: rotate(-128deg) translate(-6px, -16px); + } + 84.6% { + transform: rotate(-71deg) translate(-6px, -14px); + } + 85.5% { + transform: rotate(-71deg) translate(-6px, -14px); + } + 87.3% { + transform: rotate(-111deg) translate(-10px, -21px); + } + 100% { + transform: rotate(-111deg) translate(-10px, -21px); + } +} +@keyframes rabbit1Ear1 { + 2.7% { + transform: rotate(-40deg) translate(0, 0); + } + 3.6% { + transform: rotate(-38deg) translate(-3px, 2px); + } + 6.3% { + transform: rotate(-38deg) translate(-3px, 2px); + } + 7.2% { + transform: rotate(-38deg) translate(-3px, 4px); + } + 25.2% { + transform: rotate(-38deg) translate(-3px, 4px); + } + 26.1% { + transform: rotate(-31deg) translate(-3px, 4px); + } + 27.0% { + transform: rotate(-31deg) translate(-3px, 4px); + } + 27.9% { + transform: rotate(-19deg) translate(-3px, 7px); + } + 28.8% { + transform: rotate(-19deg) translate(-3px, 7px); + } + 29.7% { + transform: rotate(-9deg) translate(18px, 14px); + } + 30.6% { + transform: rotate(-9deg) translate(18px, 14px); + } + 31.5% { + transform: rotate(11deg) translate(-2px, 7px); + } + 32.4% { + transform: rotate(11deg) translate(-2px, 7px); + } + 33.3% { + transform: rotate(23deg) translate(-10px, 8px); + } + 34.2% { + transform: rotate(23deg) translate(-10px, 8px); + } + 35.1% { + transform: rotate(3deg) translate(-6px, 6px); + } + 36.0% { + transform: rotate(3deg) translate(-6px, 6px); + } + 36.9% { + transform: rotate(-19deg) translate(-2px, 6px); + } + 37.8% { + transform: rotate(-19deg) translate(-2px, 6px); + } + 38.7% { + transform: rotate(-24deg) translate(-2px, 6px); + } + 39.6% { + transform: rotate(-24deg) translate(-2px, 6px); + } + 40.5% { + transform: rotate(-26deg) translate(-2px, 6px); + } + 41.4% { + transform: rotate(-26deg) translate(-2px, 6px); + } + 42.3% { + transform: rotate(-21deg) translate(-2px, 6px); + } + 45.0% { + transform: rotate(-21deg) translate(-2px, 6px); + } + 45.9% { + transform: rotate(-18deg) translate(-2px, 5px); + } + 49.5% { + transform: rotate(-18deg) translate(-2px, 5px); + } + 50.4% { + transform: rotate(-5deg) translate(-11px, 10px); + } + 51.3% { + transform: rotate(-5deg) translate(-11px, 10px); + } + 52.2% { + transform: rotate(6deg) translate(-19px, 12px); + } + 53.1% { + transform: rotate(6deg) translate(-19px, 12px); + } + 54.0% { + transform: rotate(-4deg) translate(-22px, 10px); + } + 54.9% { + transform: rotate(-4deg) translate(-22px, 10px); + } + 55.8% { + transform: rotate(-21deg) translate(-15px, 10px); + } + 56.7% { + transform: rotate(-21deg) translate(-15px, 10px); + } + 57.6% { + transform: rotate(-17deg) translate(-16px, 10px); + } + 58.5% { + transform: rotate(-17deg) translate(-16px, 10px); + } + 59.4% { + transform: rotate(-14deg) translate(-16px, 10px); + } + 63.9% { + transform: rotate(-14deg) translate(-16px, 10px); + } + 64.8% { + transform: rotate(-12deg) translate(-16px, 10px); + } + 70.2% { + transform: rotate(-12deg) translate(-16px, 10px); + } + 71.1% { + transform: rotate(-12deg) translate(-15px, 7px); + } + 72.0% { + transform: rotate(-12deg) translate(-15px, 7px); + } + 72.9% { + transform: rotate(17deg) translate(-21px, 30px); + } + 73.8% { + transform: rotate(17deg) translate(-21px, 30px); + } + 74.7% { + transform: rotate(-57deg) translate(-9px, 10px); + } + 75.6% { + transform: rotate(-39deg) translate(1px, 4px); + } + 76.5% { + transform: rotate(-39deg) translate(1px, 4px); + } + 77.4% { + transform: rotate(-13deg) translate(-4px, 10px); + } + 87.3% { + transform: rotate(-13deg) translate(-4px, 10px); + } + 88.2% { + transform: rotate(-22deg) translate(-17px, 17px); + } + 80.1% { + transform: rotate(-22deg) translate(-17px, 17px); + } + 81.0% { + transform: rotate(-26deg) translate(-13px, 9px); + } + 81.9% { + transform: rotate(-29deg) translate(-14px, 9px); + } + 82.8% { + transform: rotate(-29deg) translate(-14px, 9px); + } + 84.6% { + transform: rotate(-23deg) translate(-11px, 4px); + } + 85.5% { + transform: rotate(-23deg) translate(-11px, 4px); + } + 87.3% { + transform: rotate(-31deg) translate(-11px, 4px); + } + 88.2% { + transform: rotate(-31deg) translate(-11px, 4px); + } + 90.0% { + transform: rotate(-19deg) translate(-4px, 3px); + } + 90.9% { + transform: rotate(-19deg) translate(-4px, 3px); + } + 92.7% { + transform: rotate(2deg) translate(-9px, 7px); + } + 93.6% { + transform: rotate(2deg) translate(-9px, 7px); + } + 94.5% { + transform: rotate(-1deg) translate(-9px, 7px); + } + 95.4% { + transform: rotate(-1deg) translate(-9px, 7px); + } + 96.3% { + transform: rotate(-15deg) translate(0px, 1px); + } + 100% { + transform: rotate(-15deg) translate(0px, 1px); + } +} +@keyframes rabbit1Ear1Before { + 2.7% { + transform: rotate(-20deg) translate(0, 0); + } + 3.6% { + transform: rotate(-26deg) translate(-1px, -2px); + } + 6.3% { + transform: rotate(-26deg) translate(-1px, -2px); + } + 7.2% { + transform: rotate(-25deg) translate(-1px, -2px); + } + 25.2% { + transform: rotate(-25deg) translate(-1px, -2px); + } + 26.1% { + transform: rotate(-28deg) translate(-2px, -3px); + } + 27.0% { + transform: rotate(-28deg) translate(-2px, -3px); + } + 27.9% { + transform: rotate(-35deg) translate(-3px, -5px); + } + 28.8% { + transform: rotate(-35deg) translate(-3px, -5px); + } + 29.7% { + transform: rotate(-25deg) translate(-1px, -2px); + } + 30.6% { + transform: rotate(-25deg) translate(-1px, -2px); + } + 31.5% { + transform: rotate(-29deg) translate(-1px, -3px); + } + 32.4% { + transform: rotate(-29deg) translate(-1px, -3px); + } + 33.3% { + transform: rotate(-18deg) translate(-1px, 0px); + } + 36.0% { + transform: rotate(-18deg) translate(-1px, 0px); + } + 36.9% { + transform: rotate(-11deg) translate(-1px, 0px); + } + 37.8% { + transform: rotate(-11deg) translate(-1px, 0px); + } + 38.7% { + transform: rotate(-26deg) translate(-1px, -2px); + } + 39.6% { + transform: rotate(-26deg) translate(-1px, -2px); + } + 40.5% { + transform: rotate(-41deg) translate(-2px, -6px); + } + 41.4% { + transform: rotate(-41deg) translate(-2px, -6px); + } + 42.3% { + transform: rotate(-44deg) translate(-3px, -8px); + } + 45.9% { + transform: rotate(-44deg) translate(-3px, -8px); + } + 46.8% { + transform: rotate(-42deg) translate(-3px, -8px); + } + 49.5% { + transform: rotate(-42deg) translate(-3px, -8px); + } + 50.4% { + transform: rotate(-38deg) translate(-3px, -6px); + } + 51.3% { + transform: rotate(-38deg) translate(-3px, -6px); + } + 52.2% { + transform: rotate(-31deg) translate(-2px, -3px); + } + 53.1% { + transform: rotate(-31deg) translate(-2px, -3px); + } + 54.0% { + transform: rotate(-35deg) translate(-1px, -4px); + } + 54.9% { + transform: rotate(-35deg) translate(-1px, -4px); + } + 55.8% { + transform: rotate(-29deg) translate(-3px, -4px); + } + 56.7% { + transform: rotate(-29deg) translate(-3px, -4px); + } + 57.6% { + transform: rotate(-20deg) translate(-3px, -3px); + } + 58.5% { + transform: rotate(-20deg) translate(-3px, -3px); + } + 59.4% { + transform: rotate(-21deg) translate(-3px, -3px); + } + 63.9% { + transform: rotate(-21deg) translate(-3px, -3px); + } + 64.8% { + transform: rotate(-23deg) translate(-3px, -3px); + } + 72.0% { + transform: rotate(-23deg) translate(-3px, -3px); + } + 72.9% { + transform: rotate(-8deg) translate(0px, 1px); + } + 73.8% { + transform: rotate(-8deg) translate(0px, 1px); + } + 74.7% { + transform: rotate(37deg) translate(7px, 6px); + } + 75.6% { + transform: rotate(8deg) translate(2px, 4px); + } + 87.3% { + transform: rotate(8deg) translate(2px, 4px); + } + 88.2% { + transform: rotate(38deg) translate(5px, 5px); + } + 80.1% { + transform: rotate(38deg) translate(5px, 5px); + } + 81.0% { + transform: rotate(28deg) translate(5px, 5px); + } + 81.9% { + transform: rotate(26deg) translate(5px, 5px); + } + 82.8% { + transform: rotate(26deg) translate(5px, 5px); + } + 84.6% { + transform: rotate(23deg) translate(5px, 5px); + } + 90.9% { + transform: rotate(23deg) translate(5px, 5px); + } + 92.7% { + transform: rotate(-1deg) translate(1px, 2px); + } + 93.6% { + transform: rotate(-1deg) translate(1px, 2px); + } + 94.5% { + transform: rotate(-6deg) translate(1px, 2px); + } + 100% { + transform: rotate(-6deg) translate(1px, 2px); + } +} +@keyframes rabbit1Ear2 { + 4.5% { + transform: rotate(8deg) translate(0, 0); + } + 5.4% { + transform: rotate(5deg) translate(3px, 0px); + } + 6.3% { + transform: rotate(5deg) translate(3px, 0px); + } + 7.2% { + transform: rotate(5deg) translate(4px, 0px); + } + 13.5% { + transform: rotate(5deg) translate(4px, 0px); + } + 14.4% { + transform: rotate(7deg) translate(1px, 3px); + } + 17.1% { + transform: rotate(7deg) translate(1px, 3px); + } + 18.0% { + transform: rotate(9deg) translate(-1px, 3px); + } + 25.2% { + transform: rotate(9deg) translate(-1px, 3px); + } + 26.1% { + transform: rotate(14deg) translate(-3px, 3px); + } + 27.0% { + transform: rotate(14deg) translate(-3px, 3px); + } + 27.9% { + transform: rotate(17deg) translate(0px, -1px); + } + 28.8% { + transform: rotate(17deg) translate(0px, -1px); + } + 29.7% { + transform: rotate(22deg) translate(14px, 11px); + } + 30.6% { + transform: rotate(22deg) translate(14px, 11px); + } + 31.5% { + transform: rotate(22deg) translate(4px, 0px); + } + 32.4% { + transform: rotate(22deg) translate(4px, 0px); + } + 33.3% { + transform: rotate(28deg) translate(0px, 0px); + } + 34.2% { + transform: rotate(28deg) translate(0px, 0px); + } + 35.1% { + transform: rotate(13deg) translate(4px, -3px); + } + 36.0% { + transform: rotate(13deg) translate(4px, -3px); + } + 36.9% { + transform: rotate(-1deg) translate(7px, -3px); + } + 37.8% { + transform: rotate(-1deg) translate(7px, -3px); + } + 38.7% { + transform: rotate(-4deg) translate(7px, -3px); + } + 39.6% { + transform: rotate(-4deg) translate(7px, -3px); + } + 40.5% { + transform: rotate(-8deg) translate(7px, -3px); + } + 41.4% { + transform: rotate(-8deg) translate(7px, -3px); + } + 42.3% { + transform: rotate(-5deg) translate(7px, -3px); + } + 45.0% { + transform: rotate(-5deg) translate(7px, -3px); + } + 45.9% { + transform: rotate(-2deg) translate(7px, -1px); + } + 49.5% { + transform: rotate(-2deg) translate(7px, -1px); + } + 50.4% { + transform: rotate(2deg) translate(4px, -1px); + } + 51.3% { + transform: rotate(2deg) translate(4px, -1px); + } + 52.2% { + transform: rotate(2deg) translate(4px, 1px); + } + 53.1% { + transform: rotate(2deg) translate(4px, 1px); + } + 54.0% { + transform: rotate(5deg) translate(-2px, -1px); + } + 54.9% { + transform: rotate(5deg) translate(-2px, -1px); + } + 55.8% { + transform: rotate(4deg) translate(3px, 2px); + } + 56.7% { + transform: rotate(4deg) translate(3px, 2px); + } + 57.6% { + transform: rotate(7deg) translate(-2px, -1px); + } + 58.5% { + transform: rotate(7deg) translate(-2px, -1px); + } + 59.4% { + transform: rotate(9deg) translate(-3px, -1px); + } + 61.2% { + transform: rotate(9deg) translate(-3px, -1px); + } + 63.0% { + transform: rotate(11deg) translate(-3px, -1px); + } + 70.2% { + transform: rotate(11deg) translate(-3px, -1px); + } + 71.1% { + transform: rotate(13deg) translate(-5px, -4px); + } + 72.0% { + transform: rotate(13deg) translate(-5px, -4px); + } + 72.9% { + transform: rotate(20deg) translate(-7px, 10px); + } + 73.8% { + transform: rotate(20deg) translate(-7px, 10px); + } + 74.7% { + transform: rotate(-60deg) translate(8px, 1px); + } + 75.6% { + transform: rotate(-29deg) translate(19px, 20px); + } + 76.5% { + transform: rotate(-29deg) translate(19px, 20px); + } + 77.4% { + transform: rotate(-11deg) translate(8px, 10px); + } + 87.3% { + transform: rotate(-11deg) translate(8px, 10px); + } + 88.2% { + transform: rotate(0deg) translate(-11px, -1px); + } + 80.1% { + transform: rotate(0deg) translate(-11px, -1px); + } + 81.0% { + transform: rotate(-8deg) translate(5px, -1px); + } + 81.9% { + transform: rotate(-12deg) translate(7px, -1px); + } + 82.8% { + transform: rotate(-12deg) translate(7px, -1px); + } + 84.6% { + transform: rotate(-7deg) translate(5px, -1px); + } + 85.5% { + transform: rotate(-7deg) translate(5px, -1px); + } + 87.3% { + transform: rotate(-12deg) translate(8px, -1px); + } + 88.2% { + transform: rotate(-12deg) translate(8px, -1px); + } + 90.0% { + transform: rotate(-4deg) translate(11px, 5px); + } + 90.9% { + transform: rotate(-4deg) translate(11px, 5px); + } + 92.7% { + transform: rotate(10deg) translate(11px, 5px); + } + 95.4% { + transform: rotate(10deg) translate(11px, 5px); + } + 96.3% { + transform: rotate(5deg) translate(16px, 7px); + } + 100% { + transform: rotate(5deg) translate(16px, 7px); + } +} +@keyframes rabbit1Ear2Div { + 2.7% { + transform: rotate(-34deg) translate(0, 0); + } + 3.6% { + transform: rotate(-32deg) translate(0, 0); + } + 4.5% { + transform: rotate(-32deg) translate(0, 0); + } + 5.4% { + transform: rotate(-31deg) translate(0, 0); + } + 17.1% { + transform: rotate(-31deg) translate(0, 0); + } + 18.0% { + transform: rotate(-34deg) translate(0, 0); + } + 25.2% { + transform: rotate(-34deg) translate(0, 0); + } + 26.1% { + transform: rotate(-33deg) translate(0, 0); + } + 27.0% { + transform: rotate(-33deg) translate(0, 0); + } + 27.9% { + transform: rotate(-25deg) translate(-1px, 0px); + } + 28.8% { + transform: rotate(-25deg) translate(-1px, 0px); + } + 29.7% { + transform: rotate(-13deg) translate(-2px, 5px); + } + 30.6% { + transform: rotate(-13deg) translate(-2px, 5px); + } + 31.5% { + transform: rotate(-2deg) translate(-2px, 18px); + } + 32.4% { + transform: rotate(-2deg) translate(-2px, 18px); + } + 33.3% { + transform: rotate(-15deg) translate(-4px, 14px); + } + 34.2% { + transform: rotate(-15deg) translate(-4px, 14px); + } + 35.1% { + transform: rotate(-30deg) translate(-4px, 9px); + } + 36.0% { + transform: rotate(-30deg) translate(-4px, 9px); + } + 36.9% { + transform: rotate(-62deg) translate(6px, -7px); + } + 37.8% { + transform: rotate(-62deg) translate(6px, -7px); + } + 38.7% { + transform: rotate(-66deg) translate(6px, -7px); + } + 39.6% { + transform: rotate(-66deg) translate(6px, -7px); + } + 40.5% { + transform: rotate(-69deg) translate(7px, -7px); + } + 41.4% { + transform: rotate(-69deg) translate(7px, -7px); + } + 42.3% { + transform: rotate(-74deg) translate(8px, -8px); + } + 45.9% { + transform: rotate(-74deg) translate(8px, -8px); + } + 46.8% { + transform: rotate(-72deg) translate(8px, -8px); + } + 51.3% { + transform: rotate(-72deg) translate(8px, -8px); + } + 52.2% { + transform: rotate(-61deg) translate(5px, -6px); + } + 53.1% { + transform: rotate(-61deg) translate(5px, -6px); + } + 54.0% { + transform: rotate(-81deg) translate(10px, -8px); + } + 54.9% { + transform: rotate(-81deg) translate(10px, -8px); + } + 55.8% { + transform: rotate(-96deg) translate(14px, -8px); + } + 56.7% { + transform: rotate(-96deg) translate(14px, -8px); + } + 57.6% { + transform: rotate(-94deg) translate(14px, -9px); + } + 58.5% { + transform: rotate(-94deg) translate(14px, -9px); + } + 59.4% { + transform: rotate(-88deg) translate(12px, -9px); + } + 60.3% { + transform: rotate(-88deg) translate(12px, -9px); + } + 61.2% { + transform: rotate(-86deg) translate(12px, -9px); + } + 63.0% { + transform: rotate(-88deg) translate(12px, -9px); + } + 70.2% { + transform: rotate(-88deg) translate(12px, -9px); + } + 71.1% { + transform: rotate(-89deg) translate(12px, -9px); + } + 72.0% { + transform: rotate(-89deg) translate(12px, -9px); + } + 72.9% { + transform: rotate(-66deg) translate(8px, -9px); + } + 73.8% { + transform: rotate(-66deg) translate(8px, -9px); + } + 74.7% { + transform: rotate(-43deg) translate(3px, -5px); + } + 75.6% { + transform: rotate(-58deg) translate(7px, -8px); + } + 76.5% { + transform: rotate(-58deg) translate(7px, -8px); + } + 77.4% { + transform: rotate(-40deg) translate(1px, -3px); + } + 87.3% { + transform: rotate(-40deg) translate(1px, -3px); + } + 88.2% { + transform: rotate(-33deg) translate(0px, -2px); + } + 81.0% { + transform: rotate(-33deg) translate(0px, -2px); + } + 81.9% { + transform: rotate(-36deg) translate(0px, -2px); + } + 82.8% { + transform: rotate(-36deg) translate(0px, -2px); + } + 84.6% { + transform: rotate(-34deg) translate(0px, -2px); + } + 85.5% { + transform: rotate(-34deg) translate(0px, -2px); + } + 87.3% { + transform: rotate(-44deg) translate(1px, -3px); + } + 88.2% { + transform: rotate(-44deg) translate(1px, -3px); + } + 90.0% { + transform: rotate(-32deg) translate(0px, 0px); + } + 90.9% { + transform: rotate(-32deg) translate(0px, 0px); + } + 92.7% { + transform: rotate(-59deg) translate(5px, -7px); + } + 93.6% { + transform: rotate(-59deg) translate(5px, -7px); + } + 94.5% { + transform: rotate(-68deg) translate(7px, -7px); + } + 100% { + transform: rotate(-68deg) translate(7px, -7px); + } +} +@keyframes rabbit1Ear2DivBefore { + 17.1% { + transform: rotate(5deg) translate(0, 0); + } + 18.0% { + transform: rotate(4deg) translate(0, 0); + } + 25.2% { + transform: rotate(4deg) translate(0, 0); + } + 26.1% { + transform: rotate(5deg) translate(0, 0); + } + 27.0% { + transform: rotate(5deg) translate(0, 0); + } + 27.9% { + transform: rotate(4deg) translate(0px, 4px); + } + 28.8% { + transform: rotate(4deg) translate(0px, 4px); + } + 29.7% { + transform: rotate(1deg) translate(0px, 7px); + } + 30.6% { + transform: rotate(1deg) translate(0px, 7px); + } + 31.5% { + transform: rotate(-5deg) translate(0px, 7px); + } + 32.4% { + transform: rotate(-5deg) translate(0px, 7px); + } + 33.3% { + transform: rotate(0deg) translate(1px, 4px); + } + 34.2% { + transform: rotate(0deg) translate(1px, 4px); + } + 35.1% { + transform: rotate(7deg) translate(1px, 4px); + } + 36.0% { + transform: rotate(7deg) translate(1px, 4px); + } + 36.9% { + transform: rotate(5deg) translate(-3px, 3px); + } + 39.6% { + transform: rotate(5deg) translate(-3px, 3px); + } + 40.5% { + transform: rotate(-1deg) translate(-2px, -5px); + } + 53.1% { + transform: rotate(-1deg) translate(-2px, -5px); + } + 54.0% { + transform: rotate(2deg) translate(-2px, -5px); + } + 54.9% { + transform: rotate(2deg) translate(-2px, -5px); + } + 55.8% { + transform: rotate(1deg) translate(-4px, -5px); + } + 56.7% { + transform: rotate(1deg) translate(-4px, -5px); + } + 57.6% { + transform: rotate(0deg) translate(-4px, -5px); + } + 58.5% { + transform: rotate(0deg) translate(-4px, -5px); + } + 59.4% { + transform: rotate(0deg) translate(-4px, -3px); + } + 72.0% { + transform: rotate(0deg) translate(-4px, -3px); + } + 72.9% { + transform: rotate(-3deg) translate(-4px, -3px); + } + 73.8% { + transform: rotate(-3deg) translate(-4px, -3px); + } + 74.7% { + transform: rotate(0deg) translate(-4px, 2px); + } + 75.6% { + transform: rotate(3deg) translate(-4px, 2px); + } + 76.5% { + transform: rotate(3deg) translate(-4px, 2px); + } + 77.4% { + transform: rotate(3deg) translate(-2px, 2px); + } + 87.3% { + transform: rotate(3deg) translate(-2px, 2px); + } + 88.2% { + transform: rotate(1deg) translate(-1px, 2px); + } + 88.2% { + transform: rotate(1deg) translate(-1px, 2px); + } + 90.0% { + transform: rotate(4deg) translate(-1px, 2px); + } + 90.9% { + transform: rotate(4deg) translate(-1px, 2px); + } + 92.7% { + transform: rotate(3deg) translate(-3px, 2px); + } + 93.6% { + transform: rotate(3deg) translate(-3px, 2px); + } + 94.5% { + transform: rotate(5deg) translate(-3px, 2px); + } + 100% { + transform: rotate(5deg) translate(-3px, 2px); + } +} +@keyframes rabbit1Mouth { + 0.9% { + transform: rotate(0deg) translate(0, 0); + } + 1.8% { + transform: rotate(-1deg) translate(-1px, 3px); + } + 2.7% { + transform: rotate(-1deg) translate(-1px, 3px); + } + 3.6% { + transform: rotate(-1deg) translate(-1px, 2px); + } + 4.5% { + transform: rotate(-1deg) translate(-1px, 2px); + } + 5.4% { + transform: rotate(-1deg) translate(0px, 2px); + } + 6.3% { + transform: rotate(-1deg) translate(0px, 2px); + } + 7.2% { + transform: rotate(-1deg) translate(8px, -1px); + } + 8.1% { + transform: rotate(-1deg) translate(8px, -1px); + } + 9.0% { + transform: rotate(-1deg) translate(8px, 0px); + } + 9.9% { + transform: rotate(-1deg) translate(8px, 0px); + } + 10.8% { + transform: rotate(-7deg) translate(8px, 2px); + } + 11.7% { + transform: rotate(-7deg) translate(8px, 2px); + } + 12.6% { + transform: rotate(1deg) translate(-1px, 3px); + } + 13.5% { + transform: rotate(1deg) translate(-1px, 3px); + } + 14.4% { + transform: rotate(1deg) translate(-3px, 2px); + } + 15.3% { + transform: rotate(1deg) translate(-3px, 2px); + } + 16.2% { + transform: rotate(0deg) translate(0px, 1px); + } + 17.1% { + transform: rotate(0deg) translate(0px, 1px); + } + 18.0% { + transform: rotate(0deg) translate(-2px, 2px); + } + 25.2% { + transform: rotate(0deg) translate(-2px, 2px); + } + 26.1% { + transform: rotate(0deg) translate(-8px, 9px); + } + 27.0% { + transform: rotate(0deg) translate(-8px, 9px); + } + 27.9% { + transform: rotate(1deg) translate(-9px, 17px); + } + 28.8% { + transform: rotate(1deg) translate(-9px, 17px); + } + 29.7% { + transform: rotate(-9deg) translate(-12px, 10px); + } + 30.6% { + transform: rotate(-9deg) translate(-12px, 10px); + } + 31.5% { + transform: rotate(-5deg) translate(-19px, 8px); + } + 32.4% { + transform: rotate(-5deg) translate(-19px, 8px); + } + 33.3% { + transform: rotate(3deg) translate(-13px, 7px); + } + 34.2% { + transform: rotate(3deg) translate(-13px, 7px); + } + 35.1% { + transform: rotate(3deg) translate(-11px, 7px); + } + 36.0% { + transform: rotate(3deg) translate(-11px, 7px); + } + 36.9% { + transform: rotate(-1deg) translate(-10px, 8px); + } + 37.8% { + transform: rotate(-1deg) translate(-10px, 8px); + } + 38.7% { + transform: rotate(-1deg) translate(-10px, 7px); + } + 39.6% { + transform: rotate(-1deg) translate(-10px, 7px); + } + 40.5% { + transform: rotate(-4deg) translate(-9px, 6px); + } + 41.4% { + transform: rotate(-4deg) translate(-9px, 6px); + } + 42.3% { + transform: rotate(4deg) translate(-11px, 6px); + } + 43.2% { + transform: rotate(4deg) translate(-11px, 6px); + } + 44.1% { + transform: rotate(4deg) translate(-12px, 6px); + } + 46.8% { + transform: rotate(4deg) translate(-12px, 6px); + } + 49.5% { + transform: rotate(4deg) translate(-15px, 6px); + } + 50.4% { + transform: rotate(2deg) translate(-15px, 11px); + } + 51.3% { + transform: rotate(2deg) translate(-15px, 11px); + } + 52.2% { + transform: rotate(2deg) translate(-15px, 10px); + } + 53.1% { + transform: rotate(2deg) translate(-15px, 10px); + } + 54.0% { + transform: rotate(5deg) translate(-15px, 4px); + } + 54.9% { + transform: rotate(5deg) translate(-15px, 4px); + } + 55.8% { + transform: rotate(5deg) translate(-6px, 3px); + } + 56.7% { + transform: rotate(5deg) translate(-6px, 3px); + } + 57.6% { + transform: rotate(5deg) translate(-6px, 2px); + } + 60.3% { + transform: rotate(5deg) translate(-6px, 2px); + } + 61.2% { + transform: rotate(5deg) translate(-6px, 0px); + } + 63.0% { + transform: rotate(5deg) translate(-7px, -4px); + } + 70.2% { + transform: rotate(5deg) translate(-7px, -4px); + } + 71.1% { + transform: rotate(5deg) translate(-6px, -5px); + } + 72.0% { + transform: rotate(5deg) translate(-6px, -5px); + } + 72.9% { + transform: rotate(5deg) translate(-18px, 7px); + } + 73.8% { + transform: rotate(5deg) translate(-18px, 7px); + } + 74.7% { + transform: rotate(9deg) translate(0px, -20px); + } + 75.6% { + transform: rotate(3deg) translate(3px, -19px); + } + 76.5% { + transform: rotate(3deg) translate(3px, -19px); + } + 77.4% { + transform: rotate(10deg) translate(1px, -24px); + } + 87.3% { + transform: rotate(10deg) translate(1px, -24px); + } + 88.2% { + transform: rotate(9deg) translate(1px, -25px); + } + 80.1% { + transform: rotate(9deg) translate(1px, -25px); + } + 81.0% { + transform: rotate(9deg) translate(-3px, -31px); + } + 81.9% { + transform: rotate(9deg) translate(-6px, -31px); + } + 82.8% { + transform: rotate(9deg) translate(-6px, -31px); + } + 84.6% { + transform: rotate(1deg) translate(0px, -23px); + } + 85.5% { + transform: rotate(1deg) translate(0px, -23px); + } + 87.3% { + transform: rotate(8deg) translate(-7px, -20px); + } + 88.2% { + transform: rotate(8deg) translate(-7px, -20px); + } + 90.0% { + transform: rotate(8deg) translate(-4px, -22px); + } + 90.9% { + transform: rotate(8deg) translate(-4px, -22px); + } + 92.7% { + transform: rotate(0deg) translate(0px, -15px); + } + 93.6% { + transform: rotate(0deg) translate(0px, -15px); + } + 94.5% { + transform: rotate(-1deg) translate(-6px, -12px); + } + 95.4% { + transform: rotate(-1deg) translate(-6px, -12px); + } + 96.3% { + transform: rotate(-1deg) translate(-7px, -9px); + } + 100% { + transform: rotate(-1deg) translate(-7px, -9px); + } +} +@keyframes rabbit1MouthAfter { + 0.9% { + transform: scale(1, 1) translate(0, 0) rotate(0deg); + } + 1.8% { + transform: scale(1, 1.3) translate(0px, -1px) rotate(-3deg); + } + 2.7% { + transform: scale(1, 1.3) translate(0px, -1px) rotate(-3deg); + } + 3.6% { + transform: scale(0.7, 2.5) translate(-1px, -1px) rotate(-3deg); + } + 11.7% { + transform: scale(0.7, 2.5) translate(-1px, -1px) rotate(-3deg); + } + 12.6% { + transform: scale(0.7, 3.2) translate(-1px, -2px) rotate(0deg); + } + 13.5% { + transform: scale(0.7, 3.2) translate(-1px, -2px) rotate(0deg); + } + 14.4% { + transform: scale(1, 3.2) translate(-4px, -1px) rotate(0deg); + } + 15.3% { + transform: scale(1, 3.2) translate(-4px, -1px) rotate(0deg); + } + 16.2% { + transform: scale(1.5, 3) translate(-2px, -2px) rotate(0deg); + } + 25.2% { + transform: scale(1.5, 3) translate(-2px, -2px) rotate(0deg); + } + 26.1% { + transform: scale(1, 3.3) translate(-2px, -1.5px) rotate(0deg); + } + 27.0% { + transform: scale(1, 3.3) translate(-2px, -1.5px) rotate(0deg); + } + 27.9% { + transform: scale(0.7, 3.7) translate(2px, -1.5px) rotate(0deg); + } + 28.8% { + transform: scale(0.7, 3.7) translate(2px, -1.5px) rotate(0deg); + } + 29.7% { + transform: scale(1.2, 3) translate(-1px, -1.5px) rotate(0deg); + } + 30.6% { + transform: scale(1.2, 3) translate(-1px, -1.5px) rotate(0deg); + } + 31.5% { + transform: scale(1.2, 3) translate(-3px, -2px) rotate(0deg); + } + 34.2% { + transform: scale(1.2, 3) translate(-3px, -2px) rotate(0deg); + } + 35.1% { + transform: scale(1.2, 3) translate(-6px, -1.7px) rotate(0deg); + } + 41.4% { + transform: scale(1.2, 3) translate(-6px, -1.7px) rotate(0deg); + } + 42.3% { + transform: scale(1.2, 3) translate(-9px, -1.5px) rotate(0deg); + } + 46.8% { + transform: scale(1.2, 3) translate(-9px, -1.5px) rotate(0deg); + } + 51.3% { + transform: scale(0.7, 3) translate(-2px, -1.8px) rotate(0deg); + } + 52.2% { + transform: scale(0.6, 3) translate(-4px, -1.8px) rotate(0deg); + } + 60.3% { + transform: scale(0.6, 3) translate(-4px, -1.8px) rotate(0deg); + } + 61.2% { + transform: scale(0.6, 1) translate(-4px, 3px) rotate(0deg); + } + 63.0% { + transform: scale(1, 1) translate(-4px, 2px) rotate(0deg); + } + 63.9% { + transform: scale(1, 1) translate(-4px, 2px) rotate(0deg); + } + 64.8% { + transform: scale(1, 1) translate(-4px, 1px) rotate(0deg); + } + 72.0% { + transform: scale(1, 1) translate(-4px, 1px) rotate(0deg); + } + 72.9% { + transform: scale(0.8, 2.7) translate(0px, -1.5px) rotate(0deg); + } + 73.8% { + transform: scale(0.8, 2.7) translate(0px, -1.5px) rotate(0deg); + } + 74.7% { + transform: scale(0.8, 2.7) translate(-1px, -1.5px) rotate(1deg); + } + 93.6% { + transform: scale(0.8, 2.7) translate(-1px, -1.5px) rotate(1deg); + } + 94.5% { + transform: scale(1.2, 2.7) translate(-2px, -1.5px) rotate(1deg); + } + 100% { + transform: scale(1.2, 2.7) translate(-2px, -1.5px) rotate(1deg); + } +} +@keyframes rabbit1MouthDiv { + 0.9% { + transform: translate(0px, 0) scale(1, 1) skew(0deg); + } + 1.8% { + transform: translate(0px, -8px) scale(1, 1) skew(0deg); + } + 2.7% { + transform: translate(0px, -8px) scale(1, 1) skew(0deg); + } + 3.6% { + transform: translate(0px, -12px) scale(1, 1) skew(0deg); + } + 4.5% { + transform: translate(0px, -12px) scale(1, 1) skew(0deg); + } + 5.4% { + transform: translate(-1px, -8px) scale(0.7, 1) skew(0deg); + } + 6.3% { + transform: translate(-1px, -8px) scale(0.7, 1) skew(0deg); + } + 7.2% { + transform: translate(1px, -12px) scale(0.9, 1) skew(0deg); + } + 8.1% { + transform: translate(1px, -12px) scale(0.9, 1) skew(0deg); + } + 9.0% { + transform: translate(1px, -10px) scale(1, 1) skew(0deg); + } + 9.9% { + transform: translate(1px, -10px) scale(1, 1) skew(0deg); + } + 10.8% { + transform: translate(1px, -4px) scale(1, 1) skew(0deg); + } + 11.7% { + transform: translate(1px, -4px) scale(1, 1) skew(0deg); + } + 12.6% { + transform: translate(1px, -18px) scale(1.1, 0.9) skew(0deg); + } + 13.5% { + transform: translate(1px, -18px) scale(1.1, 0.9) skew(0deg); + } + 14.4% { + transform: translate(0px, -12px) scale(0.8, 0.9) skew(0deg); + } + 15.3% { + transform: translate(0px, -12px) scale(0.8, 0.9) skew(0deg); + } + 16.2% { + transform: translate(0px, -2px) scale(0.9, 0.8) skew(0deg); + } + 17.1% { + transform: translate(0px, -2px) scale(0.9, 0.8) skew(0deg); + } + 18.0% { + transform: translate(0px, 0px) scale(0.9, 1) skew(0deg); + } + 20.7% { + transform: translate(0px, 0px) scale(0.9, 1) skew(0deg); + } + 21.6% { + transform: translate(0px, -11px) scale(1, 1) skew(0deg); + } + 22.5% { + transform: translate(0px, -11px) scale(1, 1) skew(0deg); + } + 23.4% { + transform: translate(0px, -17px) scale(1.2, 1) skew(0deg); + } + 27.0% { + transform: translate(0px, -17px) scale(1.2, 1) skew(0deg); + } + 27.9% { + transform: translate(2px, -17px) scale(1.2, 1) skew(0deg); + } + 28.8% { + transform: translate(2px, -17px) scale(1.2, 1) skew(0deg); + } + 29.7% { + transform: translate(2px, -11px) scale(1.2, 1) skew(0deg); + } + 30.6% { + transform: translate(2px, -11px) scale(1.2, 1) skew(0deg); + } + 31.5% { + transform: translate(2px, -6px) scale(1, 1) skew(0deg); + } + 32.4% { + transform: translate(2px, -6px) scale(1, 1) skew(0deg); + } + 33.3% { + transform: translate(1px, -4px) scale(1, 1) skew(0deg); + } + 34.2% { + transform: translate(1px, -4px) scale(1, 1) skew(0deg); + } + 35.1% { + transform: translate(1px, -6px) scale(0.8, 1) skew(0deg); + } + 36.0% { + transform: translate(1px, -6px) scale(0.8, 1) skew(0deg); + } + 36.9% { + transform: translate(-1px, -6px) scale(0.5, 0.6) skew(0deg); + } + 37.8% { + transform: translate(-1px, -6px) scale(0.5, 0.6) skew(0deg); + } + 38.7% { + transform: translate(1px, -6px) scale(0.7, 0.6) skew(0deg); + } + 39.6% { + transform: translate(1px, -6px) scale(0.7, 0.6) skew(0deg); + } + 40.5% { + transform: translate(2px, -9px) scale(1.1, 1) skew(0deg); + } + 41.4% { + transform: translate(2px, -9px) scale(1.1, 1) skew(0deg); + } + 42.3% { + transform: translate(-1px, -15px) scale(1.1, 1) skew(0deg); + } + 43.2% { + transform: translate(-1px, -15px) scale(1.1, 1) skew(0deg); + } + 44.1% { + transform: translate(-1px, -11px) scale(0.8, 1) skew(0deg); + } + 46.8% { + transform: translate(-1px, -11px) scale(0.8, 1) skew(0deg); + } + 49.5% { + transform: translate(0px, -17px) scale(1.1, 0.8) skew(0deg); + } + 50.4% { + transform: translate(0px, -17px) scale(0.9, 0.8) skew(0deg); + } + 53.1% { + transform: translate(0px, -17px) scale(0.9, 0.8) skew(0deg); + } + 54.0% { + transform: translate(0px, -7px) scale(0.6, 0.8) skew(0deg); + } + 54.9% { + transform: translate(0px, -7px) scale(0.6, 0.8) skew(0deg); + } + 55.8% { + transform: translate(0px, -3px) scale(0.6, 0.8) skew(0deg); + } + 56.7% { + transform: translate(0px, -3px) scale(0.6, 0.8) skew(0deg); + } + 57.6% { + transform: translate(-1px, -7px) scale(0.5, 0.5) skew(0deg); + } + 60.3% { + transform: translate(-1px, -7px) scale(0.5, 0.5) skew(0deg); + } + 61.2% { + transform: translate(-1px, -4px) scale(0.9, 0.4) skew(0deg); + } + 63.0% { + transform: translate(2px, -2px) scale(2.3, 0.7) skew(0deg); + } + 63.9% { + transform: translate(2px, -2px) scale(2.3, 0.7) skew(0deg); + } + 64.8% { + transform: translate(2px, -2px) scale(2.6, 0.8) skew(0deg); + } + 70.2% { + transform: translate(2px, -2px) scale(2.6, 0.8) skew(0deg); + } + 71.1% { + transform: translate(1px, -3px) scale(2, 0.6) skew(0deg); + } + 72.0% { + transform: translate(1px, -3px) scale(2, 0.6) skew(0deg); + } + 72.9% { + transform: translate(1px, -16px) scale(1.3, 0.8) skew(0deg); + } + 73.8% { + transform: translate(1px, -16px) scale(1.3, 0.8) skew(0deg); + } + 74.7% { + transform: translate(-3px, 10px) scale(1.3, 1.7) skew(-14deg); + } + 75.6% { + transform: translate(-3px, 12px) scale(1.3, 1.9) skew(-14deg); + } + 87.3% { + transform: translate(-3px, 12px) scale(1.3, 1.9) skew(-14deg); + } + 88.2% { + transform: translate(-3px, 10px) scale(1.3, 1.8) skew(-14deg); + } + 80.1% { + transform: translate(-3px, 10px) scale(1.3, 1.8) skew(-14deg); + } + 81.0% { + transform: translate(-3px, 13px) scale(1.5, 2.1) skew(-14deg); + } + 81.9% { + transform: translate(-4px, 17px) scale(1.5, 2.3) skew(-20deg); + } + 82.8% { + transform: translate(-4px, 17px) scale(1.5, 2.3) skew(-20deg); + } + 84.6% { + transform: translate(-4px, 9px) scale(1.3, 1.7) skew(-15deg); + } + 85.5% { + transform: translate(-4px, 9px) scale(1.3, 1.7) skew(-15deg); + } + 87.3% { + transform: translate(-3px, 15px) scale(1.3, 2.2) skew(-13deg); + } + 88.2% { + transform: translate(-3px, 15px) scale(1.3, 2.2) skew(-13deg); + } + 90.0% { + transform: translate(-3px, 12px) scale(1.3, 1.9) skew(-13deg); + } + 90.9% { + transform: translate(-3px, 12px) scale(1.3, 1.9) skew(-13deg); + } + 92.7% { + transform: translate(-2px, 4px) scale(1.3, 1.2) skew(-9deg); + } + 93.6% { + transform: translate(-2px, 4px) scale(1.3, 1.2) skew(-9deg); + } + 94.5% { + transform: translate(3px, -5px) scale(1.4, 1.2) skew(0deg); + } + 95.4% { + transform: translate(3px, -5px) scale(1.4, 1.2) skew(0deg); + } + 96.3% { + transform: translate(3px, -11px) scale(1.4, 1.2) skew(0deg); + } + 100% { + transform: translate(3px, -11px) scale(1.4, 1.2) skew(0deg); + } +} +@keyframes rabbit1Eye1 { + 2.7% { + transform: rotate(13deg) translate(0, 0); + } + 3.6% { + transform: rotate(12deg) translate(0px, -1px); + } + 4.5% { + transform: rotate(12deg) translate(0px, -1px); + } + 5.4% { + transform: rotate(8deg) translate(4px, -3px); + } + 6.3% { + transform: rotate(8deg) translate(4px, -3px); + } + 7.2% { + transform: rotate(4deg) translate(11px, -4px); + } + 9.9% { + transform: rotate(4deg) translate(11px, -4px); + } + 10.8% { + transform: rotate(4deg) translate(7px, 0px); + } + 11.7% { + transform: rotate(4deg) translate(7px, 0px); + } + 12.6% { + transform: rotate(15deg) translate(0px, -1px); + } + 13.5% { + transform: rotate(15deg) translate(0px, -1px); + } + 14.4% { + transform: rotate(15deg) translate(0px, 1px); + } + 25.2% { + transform: rotate(15deg) translate(0px, 1px); + } + 26.1% { + transform: rotate(15deg) translate(-1px, 8px); + } + 27.0% { + transform: rotate(15deg) translate(-1px, 8px); + } + 27.9% { + transform: rotate(11deg) translate(1px, 14px); + } + 28.8% { + transform: rotate(11deg) translate(1px, 14px); + } + 29.7% { + transform: rotate(11deg) translate(-9px, 16px); + } + 30.6% { + transform: rotate(11deg) translate(-9px, 16px); + } + 31.5% { + transform: rotate(11deg) translate(-11px, 14px); + } + 32.4% { + transform: rotate(11deg) translate(-11px, 14px); + } + 33.3% { + transform: rotate(11deg) translate(-9px, 11px); + } + 34.2% { + transform: rotate(11deg) translate(-9px, 11px); + } + 35.1% { + transform: rotate(11deg) translate(-8px, 10px); + } + 51.3% { + transform: rotate(11deg) translate(-8px, 10px); + } + 52.2% { + transform: rotate(11deg) translate(-8px, 13px); + } + 53.1% { + transform: rotate(11deg) translate(-8px, 13px); + } + 54.0% { + transform: rotate(11deg) translate(-6px, 3px); + } + 54.9% { + transform: rotate(11deg) translate(-6px, 3px); + } + 55.8% { + transform: rotate(11deg) translate(-3px, -4px); + } + 56.7% { + transform: rotate(11deg) translate(-3px, -4px); + } + 57.6% { + transform: rotate(11deg) translate(-2px, -6px); + } + 70.2% { + transform: rotate(11deg) translate(-2px, -6px); + } + 71.1% { + transform: rotate(11deg) translate(-3px, -7px); + } + 72.0% { + transform: rotate(11deg) translate(-3px, -7px); + } + 72.9% { + transform: rotate(11deg) translate(-9px, 12px); + } + 73.8% { + transform: rotate(11deg) translate(-9px, 12px); + } + 74.7% { + transform: rotate(11deg) translate(10px, -22px); + } + 75.6% { + transform: rotate(11deg) translate(5px, -20px); + } + 76.5% { + transform: rotate(11deg) translate(5px, -20px); + } + 77.4% { + transform: rotate(11deg) translate(8px, -25px); + } + 87.3% { + transform: rotate(11deg) translate(8px, -25px); + } + 88.2% { + transform: rotate(11deg) translate(8px, -27px); + } + 80.1% { + transform: rotate(11deg) translate(8px, -27px); + } + 81.0% { + transform: rotate(11deg) translate(9px, -31px); + } + 81.9% { + transform: rotate(11deg) translate(10px, -32px); + } + 82.8% { + transform: rotate(11deg) translate(10px, -32px); + } + 84.6% { + transform: rotate(11deg) translate(9px, -27px); + } + 85.5% { + transform: rotate(11deg) translate(9px, -27px); + } + 87.3% { + transform: rotate(11deg) translate(4px, -26px); + } + 88.2% { + transform: rotate(11deg) translate(4px, -26px); + } + 90.0% { + transform: rotate(11deg) translate(3px, -22px); + } + 90.9% { + transform: rotate(11deg) translate(3px, -22px); + } + 92.7% { + transform: rotate(7deg) translate(0px, -12px); + } + 93.6% { + transform: rotate(7deg) translate(0px, -12px); + } + 94.5% { + transform: rotate(7deg) translate(-3px, -8px); + } + 95.4% { + transform: rotate(7deg) translate(-3px, -8px); + } + 96.3% { + transform: rotate(7deg) translate(-4px, -4px); + } + 100% { + transform: rotate(7deg) translate(-4px, -4px); + } +} +@keyframes rabbit1Eye1Before { + 2.7% { + transform: rotate(-39deg) translate(0, 0); + } + 3.6% { + transform: rotate(-43deg) translate(0px, -1px); + } + 6.3% { + transform: rotate(-43deg) translate(0px, -1px); + } + 7.2% { + transform: rotate(-38deg) translate(-1px, -4px); + } + 9.9% { + transform: rotate(-38deg) translate(-1px, -4px); + } + 10.8% { + transform: rotate(-38deg) translate(1px, -4px); + } + 11.7% { + transform: rotate(-38deg) translate(1px, -4px); + } + 12.6% { + transform: rotate(-38deg) translate(0px, -1px); + } + 13.5% { + transform: rotate(-38deg) translate(0px, -1px); + } + 14.4% { + transform: rotate(-47deg) translate(7px, -5px); + } + 15.3% { + transform: rotate(-47deg) translate(7px, -5px); + } + 16.2% { + transform: rotate(-47deg) translate(10px, -6px); + } + 17.1% { + transform: rotate(-47deg) translate(10px, -6px); + } + 18.0% { + transform: rotate(-47deg) translate(11px, -6px); + } + 20.7% { + transform: rotate(-47deg) translate(11px, -6px); + } + 21.6% { + transform: rotate(-41deg) translate(8px, -6px); + } + 22.5% { + transform: rotate(-41deg) translate(8px, -6px); + } + 23.4% { + transform: rotate(-41deg) translate(6px, -5px); + } + 25.2% { + transform: rotate(-41deg) translate(6px, -5px); + } + 26.1% { + transform: rotate(-35deg) translate(2px, -7px); + } + 28.8% { + transform: rotate(-35deg) translate(2px, -7px); + } + 29.7% { + transform: rotate(-45deg) translate(10px, -3px); + } + 32.4% { + transform: rotate(-45deg) translate(10px, -3px); + } + 33.3% { + transform: rotate(-40deg) translate(7px, -5px); + } + 34.2% { + transform: rotate(-40deg) translate(7px, -5px); + } + 35.1% { + transform: rotate(-50deg) translate(8px, -3px); + } + 37.8% { + transform: rotate(-50deg) translate(8px, -3px); + } + 38.7% { + transform: rotate(-50deg) translate(8px, -4px); + } + 45.0% { + transform: rotate(-50deg) translate(8px, -4px); + } + 45.9% { + transform: rotate(-50deg) translate(8px, -5px); + } + 49.5% { + transform: rotate(-50deg) translate(8px, -5px); + } + 50.4% { + transform: rotate(-20deg) translate(2px, -5px); + } + 51.3% { + transform: rotate(-20deg) translate(2px, -5px); + } + 52.2% { + transform: rotate(0deg) translate(-3px, -3px); + } + 53.1% { + transform: rotate(0deg) translate(-3px, -3px); + } + 54.0% { + transform: rotate(-27deg) translate(1px, -4px); + } + 54.9% { + transform: rotate(-27deg) translate(1px, -4px); + } + 55.8% { + transform: rotate(-41deg) translate(2px, -2px); + } + 56.7% { + transform: rotate(-41deg) translate(2px, -2px); + } + 57.6% { + transform: rotate(-38deg) translate(1px, -1px); + } + 72.0% { + transform: rotate(-38deg) translate(1px, -1px); + } + 72.9% { + transform: rotate(2deg) translate(-2px, -3px); + } + 73.8% { + transform: rotate(2deg) translate(-2px, -3px); + } + 74.7% { + transform: rotate(-58deg) translate(7px, -2px); + } + 75.6% { + transform: rotate(-58deg) translate(7px, -1px); + } + 76.5% { + transform: rotate(-58deg) translate(7px, -1px); + } + 77.4% { + transform: rotate(-53deg) translate(7px, -1px); + } + 80.1% { + transform: rotate(-53deg) translate(7px, -1px); + } + 81.0% { + transform: rotate(-53deg) translate(8px, 1px); + } + 81.9% { + transform: rotate(-41deg) translate(6px, 1px); + } + 82.8% { + transform: rotate(-41deg) translate(6px, 1px); + } + 84.6% { + transform: rotate(-56deg) translate(7px, 0px); + } + 88.2% { + transform: rotate(-56deg) translate(7px, 0px); + } + 90.0% { + transform: rotate(-56deg) translate(8px, 5px); + } + 90.9% { + transform: rotate(-56deg) translate(8px, 5px); + } + 92.7% { + transform: rotate(-46deg) translate(-1px, -1px); + } + 95.4% { + transform: rotate(-46deg) translate(-1px, -1px); + } + 96.3% { + transform: rotate(-46deg) translate(4px, -1px); + } + 100% { + transform: rotate(-46deg) translate(4px, -1px); + } +} +@keyframes rabbit1Eye1After { + 4.5% { + transform: rotate(-9deg) translate(0, 0) scale(1, 1); + box-shadow: none; + } + 5.4% { + transform: rotate(-9deg) translate(0px, 3px) scale(1, 1); + box-shadow: none; + } + 9.9% { + transform: rotate(-9deg) translate(0px, 3px) scale(1, 1); + box-shadow: none; + } + 10.8% { + transform: rotate(-9deg) translate(-1px, 8px) scale(1, 1.4); + box-shadow: 0 1px; + } + 11.7% { + transform: rotate(-9deg) translate(-1px, 8px) scale(1, 1.4); + box-shadow: 0 1px; + } + 12.6% { + transform: rotate(-9deg) translate(-1px, 0px) scale(1, 1); + box-shadow: none; + } + 28.8% { + transform: rotate(-9deg) translate(-1px, 0px) scale(1, 1); + box-shadow: none; + } + 29.7% { + transform: rotate(-9deg) translate(1px, 0px) scale(1, 1); + box-shadow: none; + } + 30.6% { + transform: rotate(-9deg) translate(1px, 0px) scale(1, 1); + box-shadow: none; + } + 31.5% { + transform: rotate(-9deg) translate(3px, 0px) scale(1, 1); + box-shadow: none; + } + 49.5% { + transform: rotate(-9deg) translate(3px, 0px) scale(1, 1); + box-shadow: none; + } + 50.4% { + transform: rotate(-9deg) translate(-3px, 16px) scale(1, 1); + box-shadow: none; + } + 51.3% { + transform: rotate(-9deg) translate(-3px, 16px) scale(1, 1); + box-shadow: none; + } + 52.2% { + transform: rotate(-9deg) translate(-1px, 11px) scale(0.7, 1.5); + box-shadow: 0 -1px; + } + 53.1% { + transform: rotate(-9deg) translate(-1px, 11px) scale(0.7, 1.5); + box-shadow: 0 -1px; + } + 54.0% { + transform: rotate(-9deg) translate(-2px, 18px) scale(0.7, 1.5); + box-shadow: none; + } + 54.9% { + transform: rotate(-9deg) translate(-2px, 18px) scale(0.7, 1.5); + box-shadow: none; + } + 55.8% { + transform: rotate(-9deg) translate(-2px, 21px) scale(0.7, 1.5); + box-shadow: none; + } + 72.0% { + transform: rotate(-9deg) translate(-2px, 21px) scale(0.7, 1.5); + box-shadow: none; + } + 72.9% { + transform: rotate(-9deg) translate(0px, 11px) scale(0.6, 1.4); + box-shadow: 0 -1px; + } + 90.9% { + transform: rotate(-9deg) translate(0px, 11px) scale(0.6, 1.4); + box-shadow: 0 -1px; + } + 92.7% { + transform: rotate(-9deg) translate(0px, 21px) scale(0.6, 1.4); + box-shadow: none; + } + 100% { + transform: rotate(-9deg) translate(0px, 21px) scale(0.6, 1.4); + box-shadow: none; + } +} +@keyframes rabbit1Eye2 { + 2.7% { + transform: rotate(13deg) translate(0, 0); + } + 3.6% { + transform: rotate(13deg) translate(0px, -8px); + } + 4.5% { + transform: rotate(13deg) translate(0px, -8px); + } + 5.4% { + transform: rotate(4deg) translate(6px, -12px); + } + 6.3% { + transform: rotate(4deg) translate(6px, -12px); + } + 7.2% { + transform: rotate(3deg) translate(14px, -16px); + } + 9.9% { + transform: rotate(3deg) translate(14px, -16px); + } + 10.8% { + transform: rotate(3deg) translate(8px, -14px); + } + 11.7% { + transform: rotate(3deg) translate(8px, -14px); + } + 12.6% { + transform: rotate(14deg) translate(0px, -7px); + } + 13.5% { + transform: rotate(14deg) translate(0px, -7px); + } + 14.4% { + transform: rotate(14deg) translate(1px, 1px); + } + 25.2% { + transform: rotate(14deg) translate(1px, 1px); + } + 26.1% { + transform: rotate(6deg) translate(-1px, 5px); + } + 27.0% { + transform: rotate(6deg) translate(-1px, 5px); + } + 27.9% { + transform: rotate(9deg) translate(-4px, 9px); + } + 28.8% { + transform: rotate(9deg) translate(-4px, 9px); + } + 29.7% { + transform: rotate(13deg) translate(-8px, 9px); + } + 30.6% { + transform: rotate(13deg) translate(-8px, 9px); + } + 31.5% { + transform: rotate(8deg) translate(-10px, 7px); + } + 32.4% { + transform: rotate(8deg) translate(-10px, 7px); + } + 33.3% { + transform: rotate(8deg) translate(-12px, 7px); + } + 34.2% { + transform: rotate(8deg) translate(-12px, 7px); + } + 35.1% { + transform: rotate(10deg) translate(-10px, 7px); + } + 39.6% { + transform: rotate(10deg) translate(-10px, 7px); + } + 40.5% { + transform: rotate(10deg) translate(-11px, 6px); + } + 49.5% { + transform: rotate(10deg) translate(-11px, 6px); + } + 50.4% { + transform: rotate(10deg) translate(-11px, 7px); + } + 53.1% { + transform: rotate(10deg) translate(-11px, 7px); + } + 54.0% { + transform: rotate(10deg) translate(-9px, 0px); + } + 54.9% { + transform: rotate(10deg) translate(-9px, 0px); + } + 55.8% { + transform: rotate(13deg) translate(-1px, -11px); + } + 56.7% { + transform: rotate(13deg) translate(-1px, -11px); + } + 57.6% { + transform: rotate(13deg) translate(-3px, -12px); + } + 70.2% { + transform: rotate(13deg) translate(-3px, -12px); + } + 71.1% { + transform: rotate(9deg) translate(-2px, -13px); + } + 72.0% { + transform: rotate(9deg) translate(-2px, -13px); + } + 72.9% { + transform: rotate(6deg) translate(-14px, 7px); + } + 73.8% { + transform: rotate(6deg) translate(-14px, 7px); + } + 74.7% { + transform: rotate(6deg) translate(16px, -27px); + } + 75.6% { + transform: rotate(6deg) translate(15px, -26px); + } + 76.5% { + transform: rotate(6deg) translate(15px, -26px); + } + 77.4% { + transform: rotate(6deg) translate(18px, -27px); + } + 87.3% { + transform: rotate(6deg) translate(18px, -27px); + } + 88.2% { + transform: rotate(6deg) translate(16px, -28px); + } + 80.1% { + transform: rotate(6deg) translate(16px, -28px); + } + 81.0% { + transform: rotate(9deg) translate(9px, -33px); + } + 81.9% { + transform: rotate(9deg) translate(5px, -34px); + } + 82.8% { + transform: rotate(9deg) translate(5px, -34px); + } + 84.6% { + transform: rotate(9deg) translate(3px, -35px); + } + 85.5% { + transform: rotate(9deg) translate(3px, -35px); + } + 87.3% { + transform: rotate(9deg) translate(0px, -33px); + } + 88.2% { + transform: rotate(9deg) translate(0px, -33px); + } + 90.0% { + transform: rotate(9deg) translate(3px, -29px); + } + 90.9% { + transform: rotate(9deg) translate(3px, -29px); + } + 92.7% { + transform: rotate(9deg) translate(0px, -19px); + } + 93.6% { + transform: rotate(9deg) translate(0px, -19px); + } + 94.5% { + transform: rotate(9deg) translate(-3px, -16px); + } + 95.4% { + transform: rotate(9deg) translate(-3px, -16px); + } + 96.3% { + transform: rotate(9deg) translate(-4px, -14px); + } + 100% { + transform: rotate(9deg) translate(-4px, -14px); + } +} +@keyframes rabbit1Eye2Before { + 2.7% { + transform: rotate(21deg) translate(0, 0); + } + 3.6% { + transform: rotate(3deg) translate(-3px, -1px); + } + 4.5% { + transform: rotate(3deg) translate(-3px, -1px); + } + 5.4% { + transform: rotate(-2deg) translate(-2px, -3px); + } + 6.3% { + transform: rotate(-2deg) translate(-2px, -3px); + } + 7.2% { + transform: rotate(-2deg) translate(-7px, -4px); + } + 9.9% { + transform: rotate(-2deg) translate(-7px, -4px); + } + 10.8% { + transform: rotate(-1deg) translate(-3px, -2px); + } + 13.5% { + transform: rotate(-1deg) translate(-3px, -2px); + } + 14.4% { + transform: rotate(-1deg) translate(0px, -12px); + } + 15.3% { + transform: rotate(-1deg) translate(0px, -12px); + } + 16.2% { + transform: rotate(-1deg) translate(3px, -13px); + } + 17.1% { + transform: rotate(-1deg) translate(3px, -13px); + } + 18.0% { + transform: rotate(-1deg) translate(3px, -15px); + } + 20.7% { + transform: rotate(-1deg) translate(3px, -15px); + } + 21.6% { + transform: rotate(9deg) translate(1px, -13px); + } + 22.5% { + transform: rotate(9deg) translate(1px, -13px); + } + 23.4% { + transform: rotate(14deg) translate(-1px, -11px); + } + 25.2% { + transform: rotate(14deg) translate(-1px, -11px); + } + 26.1% { + transform: rotate(7deg) translate(1px, -8px); + } + 27.0% { + transform: rotate(7deg) translate(1px, -8px); + } + 27.9% { + transform: rotate(-22deg) translate(-2px, -5px); + } + 28.8% { + transform: rotate(-22deg) translate(-2px, -5px); + } + 29.7% { + transform: rotate(8deg) translate(-4px, -7px); + } + 30.6% { + transform: rotate(8deg) translate(-4px, -7px); + } + 31.5% { + transform: rotate(15deg) translate(-2px, -8px); + } + 32.4% { + transform: rotate(15deg) translate(-2px, -8px); + } + 33.3% { + transform: rotate(5deg) translate(-2px, -8px); + } + 37.8% { + transform: rotate(5deg) translate(-2px, -8px); + } + 38.7% { + transform: rotate(1deg) translate(-7px, -7px); + } + 45.0% { + transform: rotate(1deg) translate(-7px, -7px); + } + 45.9% { + transform: rotate(1deg) translate(-7px, -6px); + } + 49.5% { + transform: rotate(1deg) translate(-7px, -6px); + } + 50.4% { + transform: rotate(-10deg) translate(-4px, -5px); + } + 51.3% { + transform: rotate(-10deg) translate(-4px, -5px); + } + 52.2% { + transform: rotate(-10deg) translate(-4px, 1px); + } + 53.1% { + transform: rotate(-10deg) translate(-4px, 1px); + } + 54.0% { + transform: rotate(-16deg) translate(-1px, -4px); + } + 54.9% { + transform: rotate(-16deg) translate(-1px, -4px); + } + 55.8% { + transform: rotate(14deg) translate(-2px, -2px); + } + 56.7% { + transform: rotate(14deg) translate(-2px, -2px); + } + 57.6% { + transform: rotate(4deg) translate(-3px, -2px); + } + 72.0% { + transform: rotate(4deg) translate(-3px, -2px); + } + 72.9% { + transform: rotate(-9deg) translate(-2px, 0px); + } + 73.8% { + transform: rotate(-9deg) translate(-2px, 0px); + } + 74.7% { + transform: rotate(13deg) translate(-2px, -5px); + } + 81.0% { + transform: rotate(13deg) translate(-2px, -5px); + } + 81.9% { + transform: rotate(16deg) translate(-4px, -6px); + } + 90.9% { + transform: rotate(16deg) translate(-4px, -6px); + } + 92.7% { + transform: rotate(16deg) translate(-4px, 0px); + } + 100% { + transform: rotate(16deg) translate(-4px, 0px); + } +} +@keyframes rabbit1Eye2After { + 4.5% { + transform: rotate(-9deg) translate(0, 0) scale(1, 1); + box-shadow: none; + } + 5.4% { + transform: rotate(-9deg) translate(1px, 3px) scale(1, 1); + box-shadow: none; + } + 9.9% { + transform: rotate(-9deg) translate(1px, 3px) scale(1, 1); + box-shadow: none; + } + 10.8% { + transform: rotate(-9deg) translate(-1px, 8px) scale(1, 1.4); + box-shadow: 0 1px; + } + 11.7% { + transform: rotate(-9deg) translate(-1px, 8px) scale(1, 1.4); + box-shadow: 0 1px; + } + 12.6% { + transform: rotate(-9deg) translate(-1px, -1px) scale(1, 1.4); + box-shadow: none; + } + 49.5% { + transform: rotate(-9deg) translate(-1px, -1px) scale(1, 1.4); + box-shadow: none; + } + 50.4% { + transform: rotate(-9deg) translate(-1px, 16px) scale(1, 1); + box-shadow: none; + } + 51.3% { + transform: rotate(-9deg) translate(-1px, 16px) scale(1, 1); + box-shadow: none; + } + 52.2% { + transform: rotate(-9deg) translate(0px, 11px) scale(1, 1.5); + box-shadow: 0 -1px; + } + 53.1% { + transform: rotate(-9deg) translate(0px, 11px) scale(1, 1.5); + box-shadow: 0 -1px; + } + 54.0% { + transform: rotate(-9deg) translate(-2px, 18px) scale(1, 1.5); + box-shadow: none; + } + 54.9% { + transform: rotate(-9deg) translate(-2px, 18px) scale(1, 1.5); + box-shadow: none; + } + 55.8% { + transform: rotate(-9deg) translate(-2px, 22px) scale(1, 1.5); + box-shadow: none; + } + 72.0% { + transform: rotate(-9deg) translate(-2px, 22px) scale(1, 1.5); + box-shadow: none; + } + 72.9% { + transform: rotate(-9deg) translate(-1px, 11px) scale(0.9, 1.5); + box-shadow: 0 -1px; + } + 90.9% { + transform: rotate(-9deg) translate(-1px, 11px) scale(0.9, 1.5); + box-shadow: 0 -1px; + } + 92.7% { + transform: rotate(-9deg) translate(-1px, 21px) scale(0.9, 1.5); + box-shadow: none; + } + 100% { + transform: rotate(-9deg) translate(-1px, 21px) scale(0.9, 1.5); + box-shadow: none; + } +} +@keyframes rabbit1Nose { + 2.7% { + transform: rotate(11deg) translate(0, 0); + } + 3.6% { + transform: rotate(4deg) translate(0px, -1px); + } + 4.5% { + transform: rotate(4deg) translate(0px, -1px); + } + 5.4% { + transform: rotate(2deg) translate(7px, -2px); + } + 6.3% { + transform: rotate(2deg) translate(7px, -2px); + } + 7.2% { + transform: rotate(-2deg) translate(11px, -3px); + } + 9.9% { + transform: rotate(-2deg) translate(11px, -3px); + } + 10.8% { + transform: rotate(-5deg) translate(7px, -1px); + } + 11.7% { + transform: rotate(-5deg) translate(7px, -1px); + } + 12.6% { + transform: rotate(0deg) translate(-1px, -1px); + } + 13.5% { + transform: rotate(0deg) translate(-1px, -1px); + } + 14.4% { + transform: rotate(7deg) translate(-1px, 1px); + } + 25.2% { + transform: rotate(7deg) translate(-1px, 1px); + } + 26.1% { + transform: rotate(13deg) translate(-2px, 8px); + } + 27.0% { + transform: rotate(13deg) translate(-2px, 8px); + } + 27.9% { + transform: rotate(8deg) translate(-3px, 18px); + } + 28.8% { + transform: rotate(8deg) translate(-3px, 18px); + } + 29.7% { + transform: rotate(2deg) translate(-11px, 11px); + } + 30.6% { + transform: rotate(2deg) translate(-11px, 11px); + } + 31.5% { + transform: rotate(4deg) translate(-12px, 9px); + } + 32.4% { + transform: rotate(4deg) translate(-12px, 9px); + } + 33.3% { + transform: rotate(4deg) translate(-14px, 8px); + } + 34.2% { + transform: rotate(4deg) translate(-14px, 8px); + } + 35.1% { + transform: rotate(4deg) translate(-12px, 8px); + } + 37.8% { + transform: rotate(4deg) translate(-12px, 8px); + } + 38.7% { + transform: rotate(4deg) translate(-13px, 7px); + } + 53.1% { + transform: rotate(4deg) translate(-13px, 7px); + } + 54.0% { + transform: rotate(4deg) translate(-10px, 1px); + } + 54.9% { + transform: rotate(4deg) translate(-10px, 1px); + } + 55.8% { + transform: rotate(1deg) translate(-3px, -2px); + } + 63.9% { + transform: rotate(1deg) translate(-3px, -2px); + } + 64.8% { + transform: rotate(6deg) translate(-3px, -3px); + } + 70.2% { + transform: rotate(6deg) translate(-3px, -3px); + } + 71.1% { + transform: rotate(2deg) translate(-3px, -4px); + } + 72.0% { + transform: rotate(2deg) translate(-3px, -4px); + } + 72.9% { + transform: rotate(7deg) translate(-15px, 6px); + } + 73.8% { + transform: rotate(7deg) translate(-15px, 6px); + } + 74.7% { + transform: rotate(5deg) translate(9px, -22px); + } + 75.6% { + transform: rotate(1deg) translate(6px, -20px); + } + 76.5% { + transform: rotate(1deg) translate(6px, -20px); + } + 77.4% { + transform: rotate(1deg) translate(9px, -23px); + } + 87.3% { + transform: rotate(1deg) translate(9px, -23px); + } + 88.2% { + transform: rotate(5deg) translate(7px, -26px); + } + 80.1% { + transform: rotate(5deg) translate(7px, -26px); + } + 81.0% { + transform: rotate(5deg) translate(5px, -31px); + } + 81.9% { + transform: rotate(5deg) translate(1px, -31px); + } + 82.8% { + transform: rotate(5deg) translate(1px, -31px); + } + 84.6% { + transform: rotate(-3deg) translate(3px, -28px); + } + 85.5% { + transform: rotate(-3deg) translate(3px, -28px); + } + 87.3% { + transform: rotate(-3deg) translate(-1px, -27px); + } + 88.2% { + transform: rotate(-3deg) translate(-1px, -27px); + } + 90.0% { + transform: rotate(-3deg) translate(2px, -23px); + } + 90.9% { + transform: rotate(-3deg) translate(2px, -23px); + } + 92.7% { + transform: rotate(2deg) translate(0px, -15px); + } + 93.6% { + transform: rotate(2deg) translate(0px, -15px); + } + 94.5% { + transform: rotate(2deg) translate(-4px, -12px); + } + 95.4% { + transform: rotate(2deg) translate(-4px, -12px); + } + 96.3% { + transform: rotate(2deg) translate(-5px, -9px); + } + 100% { + transform: rotate(2deg) translate(-5px, -9px); + } +} + +@keyframes lightning { + 0% { + transform: scale(1); + } + 20% { + transform: scale(0.9); + } + 40% { + transform: scale(1); + } + 70% { + transform: scale(0.8); + } +} diff --git a/packages/web/src/components/Event/SuggestJoinEventContainer/index.tsx b/packages/web/src/components/Event/SuggestJoinEventContainer/index.tsx new file mode 100644 index 000000000..23ee8fd43 --- /dev/null +++ b/packages/web/src/components/Event/SuggestJoinEventContainer/index.tsx @@ -0,0 +1,100 @@ +import { memo, useState } from "react"; + +import { useIsLogin, useValueRecoilState } from "@/hooks/useFetchRecoilState"; + +import Button from "@/components/Button"; +import { + ModalEvent2024SpringJoin, + ModalNotification, +} from "@/components/ModalPopup"; +import WhiteContainer from "@/components/WhiteContainer"; + +import eventTheme from "@/tools/eventTheme"; +import theme from "@/tools/theme"; + +const SuggestJoinEventContainer = () => { + const isLogin = useIsLogin(); + const { isAgreeOnTermsOfEvent, completedQuests } = + useValueRecoilState("event2024SpringInfo") || {}; + + const [isOpenJoin, setIsOpenJoin] = useState(false); + const [isOpenNotification, setIsOpenNotification] = useState(false); + + const styleButton = { + padding: "12px", + borderRadius: "12px", + ...eventTheme.font12_bold, + background: eventTheme.blue_title, + }; + const styleTitle = { + ...eventTheme.font16_bold, + background: eventTheme.blue_title, + backgroundClip: "text", + textFillColor: "transparent", + marginBottom: "4px", + }; + const styleDescription = { + ...eventTheme.font10, + color: theme.white, + margin: "12px", + }; + + return ( + <> + {!isLogin ? null : !isAgreeOnTermsOfEvent ? ( + +
🌟 첫 발걸음
+
+ 이벤트 참여 동의 이후 퀘스트 달성이 가능합니다. 많은 혜택과 기회를 + 놓치지 마세요! +
+ +
+ ) : completedQuests && !completedQuests.includes("adPushAgreement") ? ( + +
🌟 Taxi의 소울메이트
+
+ Taxi 서비스를 잊지 않도록 가끔 찾아갈게요! 광고성 푸시 알림 수신 + 동의를 해주시면 방이 많이 모이는 시즌, 주변에 택시앱 사용자가 있을 + 때 알려드릴 수 있어요. +
+ +
+ ) : null} + + + + ); +}; + +export default memo(SuggestJoinEventContainer); diff --git a/packages/web/src/components/Event/WhiteContainerSuggestJoinEvent/index.tsx b/packages/web/src/components/Event/WhiteContainerSuggestJoinEvent/index.tsx new file mode 100644 index 000000000..4bc543d32 --- /dev/null +++ b/packages/web/src/components/Event/WhiteContainerSuggestJoinEvent/index.tsx @@ -0,0 +1,122 @@ +import { useMemo, useState } from "react"; + +import { useIsLogin, useValueRecoilState } from "@/hooks/useFetchRecoilState"; + +import Button from "@/components/Button"; +import LinkEvent2023FallInstagramStoryShare from "@/components/Link/LinkEvent2023FallInstagramStoryShare"; +import { + ModalEvent2023FallJoin, + ModalNotification, +} from "@/components/ModalPopup"; +import WhiteContainer from "@/components/WhiteContainer"; + +import { deviceType } from "@/tools/loadenv"; +import theme from "@/tools/theme"; + +const WhiteContainerSuggestJoinEvent = () => { + const isLogin = useIsLogin(); + const { isAgreeOnTermsOfEvent, completedQuests } = + useValueRecoilState("event2023FallInfo") || {}; + + const randomToken = useMemo(() => !!Math.floor(Math.random() * 2), []); + const [isOpenJoin, setIsOpenJoin] = useState(false); + const [isOpenNotification, setIsOpenNotification] = useState(false); + + const styleText = { + ...theme.font14, + marginBottom: "12px", + }; + const styleButton = { + padding: "14px 0 13px", + borderRadius: "12px", + ...theme.font14_bold, + }; + + return ( + <> + {!isLogin ? null : !isAgreeOnTermsOfEvent ? ( + +
+ 🌟 첫 발걸음 +
+
+ 이벤트 참여 동의 이후 퀘스트 달성 및 달토끼 상점 이용이 가능합니다. + 많은 혜택과 기회를 놓치지 마세요! +
+ +
+ ) : randomToken && + completedQuests && + !completedQuests.includes("adPushAgreement") ? ( + +
+ 🌟 Taxi의 소울메이트 +
+
+ Taxi 서비스를 잊지 않도록 가끔 찾아갈게요! 광고성 푸시 알림 수신 + 동의를 해주시면 방이 많이 모이는 시즌, 주변에 택시앱 사용자가 있을 + 때 알려드릴 수 있어요. +
+ +
+ ) : completedQuests && + !completedQuests.includes("eventSharingOnInstagram") && + deviceType.startsWith("app/") ? ( + +
+ 🌟 나만 알기에는 아까운 이벤트 +
+
+ 추석에 맞춰 쏟아지는 혜택들. 나만 알 순 없죠. 인스타그램 친구들에게 + 스토리로 공유해보아요. +
+ + + +
+ ) : completedQuests && !completedQuests.includes("adPushAgreement") ? ( + +
+ 🌟 Taxi의 소울메이트 +
+
+ Taxi 서비스를 잊지 않도록 가끔 찾아갈게요! 광고성 푸시 알림 수신 + 동의를 해주시면 방이 많이 모이는 시즌, 주변에 택시앱 사용자가 있을 + 때 알려드릴 수 있어요. +
+ +
+ ) : null} + + + + ); +}; + +export default WhiteContainerSuggestJoinEvent; diff --git a/packages/web/src/components/Footer/ButtonAboveFooter.tsx b/packages/web/src/components/Footer/ButtonAboveFooter.tsx new file mode 100644 index 000000000..d1a8d323d --- /dev/null +++ b/packages/web/src/components/Footer/ButtonAboveFooter.tsx @@ -0,0 +1,23 @@ +import theme from "@/tools/theme"; + +type ButtonAboveFooterProps = { + text: string; + onClick?: () => void; +}; + +const ButtonAboveFooter = ({ text, onClick }: ButtonAboveFooterProps) => ( + +); + +export default ButtonAboveFooter; diff --git a/packages/web/src/components/Footer/index.stories.tsx b/packages/web/src/components/Footer/index.stories.tsx new file mode 100644 index 000000000..f66da2aad --- /dev/null +++ b/packages/web/src/components/Footer/index.stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import Footer from "./index"; + +const meta: Meta = { + component: Footer, + parameters: { + layout: "centered", + }, + argTypes: { + type: { + control: { + type: "select", + options: ["only-logo", "full", "event-2023fall"], + }, + }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + args: { + type: "full", + }, + render: (args) =>
, +}; diff --git a/packages/web/src/components/Footer/index.tsx b/packages/web/src/components/Footer/index.tsx new file mode 100644 index 000000000..0a2a38ad5 --- /dev/null +++ b/packages/web/src/components/Footer/index.tsx @@ -0,0 +1,119 @@ +import { ReactNode, memo, useCallback, useState } from "react"; +import { Link } from "react-router-dom"; + +import { ModalCredit, ModalPrivacyPolicy } from "@/components/ModalPopup"; + +import ButtonAboveFooter from "./ButtonAboveFooter"; + +import { ReactComponent as SparcsLogo } from "@/static/assets/sparcsLogos/SparcsLogoWithText.svg"; +import { ReactComponent as SparcsLogoWhite } from "@/static/events/2024SparcsLogoWithTextWhite.svg"; + +type FooterProps = { + type?: "only-logo" | "full" | "event-2023fall" | "event-2024spring"; + children?: ReactNode; +}; + +const Footer = ({ type = "full", children }: FooterProps) => { + const [isOpenPrivacyPolicy, setIsOpenPrivacyPolicy] = useState(false); + const [isOpenCredit, setIsOpenCredit] = useState(false); + + const onClickPrivacyPolicy = useCallback( + () => setIsOpenPrivacyPolicy(true), + [setIsOpenPrivacyPolicy] + ); + const onClickCredit = useCallback( + () => setIsOpenCredit(true), + [setIsOpenCredit] + ); + + return ( +
+ {children} + {type === "full" && ( + <> + + + + + + + + + + + + )} + {type === "event-2023fall" && ( + <> + + + + + + + + + + )} + {type === "event-2024spring" && ( + <> + + + + + + + + + + +
+ + + +
+ + )} + {type !== "event-2024spring" && ( +
+ + + +
+ )} +
+ ); +}; + +export default memo(Footer); diff --git a/src/components/HeaderBar.tsx b/packages/web/src/components/Header/HeaderBar.tsx similarity index 91% rename from src/components/HeaderBar.tsx rename to packages/web/src/components/Header/HeaderBar.tsx index c6e507004..86798dae4 100644 --- a/src/components/HeaderBar.tsx +++ b/packages/web/src/components/Header/HeaderBar.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; type HeaderBarProps = { position?: "fixed" | "absolute"; diff --git a/packages/web/src/components/Header/HeaderWithBackButton.tsx b/packages/web/src/components/Header/HeaderWithBackButton.tsx new file mode 100644 index 000000000..790a4b181 --- /dev/null +++ b/packages/web/src/components/Header/HeaderWithBackButton.tsx @@ -0,0 +1,56 @@ +import { ReactNode } from "react"; +import { useHistory } from "react-router-dom"; + +import AdaptiveDiv from "@/components/AdaptiveDiv"; + +import Header from "."; + +import theme from "@/tools/theme"; + +import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; + +type HeaderWithBackButtonProps = { + isDisplayBackBtn?: boolean; + children?: ReactNode; +} & Omit[0], "type">; + +const HeaderWithBackButton = ({ + isDisplayBackBtn = true, + children, + ...adaptiveDivProps +}: HeaderWithBackButtonProps) => { + const history = useHistory(); + + const styleBody = { + height: "100%", + display: "flex", + gap: "16px", + alignItems: "center", + }; + const styleIconLarge = { + fill: theme.purple, + ...theme.cursor(), + width: "24px", + height: "24px", + }; + + return ( +
+ + {isDisplayBackBtn && ( + history.replace("/myroom") + : () => history.goBack() + } + /> + )} +
{children}
+
+
+ ); +}; + +export default HeaderWithBackButton; diff --git a/packages/web/src/components/Header/HeaderWithLeftNav.tsx b/packages/web/src/components/Header/HeaderWithLeftNav.tsx new file mode 100644 index 000000000..a72d89409 --- /dev/null +++ b/packages/web/src/components/Header/HeaderWithLeftNav.tsx @@ -0,0 +1,66 @@ +import { ReactNode, memo } from "react"; +import { Link } from "react-router-dom"; + +import HeaderWithBackButton from "./HeaderWithBackButton"; + +import theme from "@/tools/theme"; + +type ButtonNavProps = { + selected?: boolean; + children?: ReactNode; +}; + +const ButtonNav = ({ selected, children }: ButtonNavProps) => ( +
+
+ {children} +
+
+
+
+
+); + +type HeaderWithLeftNavProps = { + value?: string; + options?: Array<{ value: string; label: string; to: string }>; +}; + +const HeaderWithLeftNav = ({ value, options = [] }: HeaderWithLeftNavProps) => ( + +
+
+ {options.map(({ value: _value, label, to }) => ( + + + {label} + + + ))} +
+ +); + +export default memo(HeaderWithLeftNav); diff --git a/packages/web/src/components/Header/index.tsx b/packages/web/src/components/Header/index.tsx new file mode 100644 index 000000000..af624e638 --- /dev/null +++ b/packages/web/src/components/Header/index.tsx @@ -0,0 +1,53 @@ +import { HTMLProps, ReactNode, memo, useEffect, useRef, useState } from "react"; + +import theme from "@/tools/theme"; + +type HeaderProps = { + children?: ReactNode; +} & HTMLProps; + +const Header = ({ children, ...divProps }: HeaderProps) => { + const navRef = useRef(null); + const padRef = useRef(null); + const [padHeight, setPadHeight] = useState(0); + + useEffect( + () => + setPadHeight( + (navRef.current?.offsetHeight || 0) - (padRef.current?.offsetTop || 0) + ), + [] + ); + + const style = { + position: "fixed" as const, + left: 0, + top: 0, + width: "100%", + height: "40px", + zIndex: theme.zIndex_nav, + overflow: "hidden", + background: theme.white, + boxShadow: theme.shadow_3, + padding: "calc(max(5px, env(safe-area-inset-top)) + 12px) 0 12px", + display: "flex", + gap: "16px", + alignItems: "center", + }; + + return ( + <> +
+ {children} +
+
+ + ); +}; + +export default memo(Header); diff --git a/src/components/Input/InputAccount.tsx b/packages/web/src/components/Input/InputAccount.tsx similarity index 97% rename from src/components/Input/InputAccount.tsx rename to packages/web/src/components/Input/InputAccount.tsx index 6c53f5b7d..195ee120b 100644 --- a/src/components/Input/InputAccount.tsx +++ b/packages/web/src/components/Input/InputAccount.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react"; import Input from "."; import Select from "./Select"; -import bankNames from "static/bankNames"; +import bankNames from "@/static/bankNames"; type InputAcountProps = { value: string; diff --git a/src/components/Input/Select.tsx b/packages/web/src/components/Input/Select.tsx similarity index 97% rename from src/components/Input/Select.tsx rename to packages/web/src/components/Input/Select.tsx index f3fc625bc..7cce4fbd8 100644 --- a/src/components/Input/Select.tsx +++ b/packages/web/src/components/Input/Select.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; import ArrowDropDownRoundedIcon from "@mui/icons-material/ArrowDropDownRounded"; diff --git a/src/components/Input/index.tsx b/packages/web/src/components/Input/index.tsx similarity index 95% rename from src/components/Input/index.tsx rename to packages/web/src/components/Input/index.tsx index 47e4f9fa7..a398ba88c 100644 --- a/src/components/Input/index.tsx +++ b/packages/web/src/components/Input/index.tsx @@ -1,6 +1,6 @@ import { HTMLProps } from "react"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; type InputProps = { value?: string; diff --git a/src/components/Link/LinkCallTaxi.tsx b/packages/web/src/components/Link/LinkCallTaxi.tsx similarity index 91% rename from src/components/Link/LinkCallTaxi.tsx rename to packages/web/src/components/Link/LinkCallTaxi.tsx index 09f3c6686..574545c93 100644 --- a/src/components/Link/LinkCallTaxi.tsx +++ b/packages/web/src/components/Link/LinkCallTaxi.tsx @@ -1,6 +1,6 @@ -import type { Location } from "types/location"; +import type { Location } from "@/types/location"; -import taxiLocationsAtom from "atoms/taxiLocations"; +import taxiLocationsAtom from "@/atoms/taxiLocations"; import { useRecoilValue } from "recoil"; type LinkCallTaxiProps = { diff --git a/src/components/Link/LinkCopy.tsx b/packages/web/src/components/Link/LinkCopy.tsx similarity index 61% rename from src/components/Link/LinkCopy.tsx rename to packages/web/src/components/Link/LinkCopy.tsx index 9f9020b1c..32acc72d4 100644 --- a/src/components/Link/LinkCopy.tsx +++ b/packages/web/src/components/Link/LinkCopy.tsx @@ -1,10 +1,10 @@ import { useCallback } from "react"; -import alertAtom from "atoms/alert"; -import isAppAtom from "atoms/isApp"; -import { useRecoilValue, useSetRecoilState } from "recoil"; +import alertAtom from "@/atoms/alert"; +import { useSetRecoilState } from "recoil"; -import { sendClipboardCopyEventToFlutter } from "tools/sendEventToFlutter"; +import { deviceType } from "@/tools/loadenv"; +import { sendClipboardCopyEventToFlutter } from "@/tools/sendEventToFlutter"; type LinkCopyProps = { children: React.ReactNode; @@ -14,17 +14,15 @@ type LinkCopyProps = { const LinkCopy = ({ children, value, onCopy }: LinkCopyProps) => { const setAlert = useSetRecoilState(alertAtom); - const isApp = useRecoilValue(isAppAtom); - const onClick = useCallback(() => { - if (isApp) sendClipboardCopyEventToFlutter(value); + if (deviceType.startsWith("app/")) sendClipboardCopyEventToFlutter(value); if (!navigator.clipboard) { setAlert("복사를 지원하지 않는 브라우저입니다."); return; } navigator.clipboard.writeText(value); onCopy?.(value); - }, [isApp, value, setAlert, onCopy]); + }, [value, onCopy]); return {children}; }; diff --git a/packages/web/src/components/Link/LinkEvent2023FallInstagramStoryShare.tsx b/packages/web/src/components/Link/LinkEvent2023FallInstagramStoryShare.tsx new file mode 100644 index 000000000..a32d9066f --- /dev/null +++ b/packages/web/src/components/Link/LinkEvent2023FallInstagramStoryShare.tsx @@ -0,0 +1,83 @@ +import { HTMLAttributes, ReactNode, useCallback, useState } from "react"; + +import { useEvent2023FallQuestComplete } from "@/hooks/event/useEvent2023FallQuestComplete"; +import { sendPopupInstagramStoryShareToFlutter } from "@/hooks/skeleton/useFlutterEventCommunicationEffect"; +import { useIsLogin, useValueRecoilState } from "@/hooks/useFetchRecoilState"; + +import ModalEvent2023FallJoin from "@/components/ModalPopup/ModalEvent2023FallJoin"; + +import alertAtom from "@/atoms/alert"; +import { useSetRecoilState } from "recoil"; + +import { deviceType } from "@/tools/loadenv"; + +const backgroundLayerDefaultUrl = + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/instagram_background.png"; +const stickerLayerDefaultUrl = + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/instagram_sticker.png"; + +type LinkEvent2023FallInstagramStoryShareProps = { + type: "eventSharingOnInstagram" | "purchaseSharingOnInstagram"; + backgroundLayerUrl?: string; + stickerLayerUrl?: string; + children?: ReactNode; +} & HTMLAttributes; + +const LinkEvent2023FallInstagramStoryShare = ({ + type, + backgroundLayerUrl = backgroundLayerDefaultUrl, + stickerLayerUrl = stickerLayerDefaultUrl, + children, + ...aProps +}: LinkEvent2023FallInstagramStoryShareProps) => { + const setAlert = useSetRecoilState(alertAtom); + const isLogin = useIsLogin(); + const { isAgreeOnTermsOfEvent } = + useValueRecoilState("event2023FallInfo") || {}; + const [isOpenJoin, setIsOpenJoin] = useState(false); + + //#region event2023Fall + const event2023FallQuestComplete = useEvent2023FallQuestComplete(); + //#endregion + + const onClick = useCallback(async () => { + if (!deviceType.startsWith("app/")) { + setAlert("앱에서만 이용 가능합니다."); + } else if (!isLogin) { + setAlert("로그인 이후 이용해주세요."); + } else if (!isAgreeOnTermsOfEvent) { + setIsOpenJoin(true); + } else { + const result = await sendPopupInstagramStoryShareToFlutter({ + backgroundLayerUrl, + stickerLayerUrl, + }); + if (result) { + event2023FallQuestComplete(type); + } else { + setAlert("인스타그램 실행에 실패하였습니다."); + return; + } + } + }, [ + isLogin, + isAgreeOnTermsOfEvent, + backgroundLayerUrl, + stickerLayerUrl, + event2023FallQuestComplete, + ]); + + return ( + <> + + {children} + + + + ); +}; + +export default LinkEvent2023FallInstagramStoryShare; diff --git a/src/components/Link/LinkKakaotalkShare.tsx b/packages/web/src/components/Link/LinkKakaotalkShare.tsx similarity index 97% rename from src/components/Link/LinkKakaotalkShare.tsx rename to packages/web/src/components/Link/LinkKakaotalkShare.tsx index 9293dfae3..5891a62a7 100644 --- a/src/components/Link/LinkKakaotalkShare.tsx +++ b/packages/web/src/components/Link/LinkKakaotalkShare.tsx @@ -1,7 +1,7 @@ import { useCallback } from "react"; import { useLocation } from "react-router-dom"; -import { kakaoSDKKey } from "loadenv"; +import { kakaoSDKKey } from "@/tools/loadenv"; type LinkKakaotalkShareProps = { children: React.ReactNode; diff --git a/src/components/Link/LinkLogin.tsx b/packages/web/src/components/Link/LinkLogin.tsx similarity index 71% rename from src/components/Link/LinkLogin.tsx rename to packages/web/src/components/Link/LinkLogin.tsx index d8e75f8db..cf1a2ca62 100644 --- a/src/components/Link/LinkLogin.tsx +++ b/packages/web/src/components/Link/LinkLogin.tsx @@ -1,9 +1,6 @@ import { useLocation } from "react-router-dom"; -import isAppAtom from "atoms/isApp"; -import { useRecoilValue } from "recoil"; - -import { backServer } from "loadenv"; +import { backServer, deviceType } from "@/tools/loadenv"; type LinkLoginProps = { children: React.ReactNode; @@ -12,14 +9,13 @@ type LinkLoginProps = { const LinkLogin = ({ children, redirect }: LinkLoginProps) => { const { pathname, search } = useLocation(); - const isApp = useRecoilValue(isAppAtom); const redirectPath = redirect || pathname + search; return ( {children} diff --git a/src/components/Link/LinkLogout.tsx b/packages/web/src/components/Link/LinkLogout.tsx similarity index 74% rename from src/components/Link/LinkLogout.tsx rename to packages/web/src/components/Link/LinkLogout.tsx index fd6a51d13..a3fbb9249 100644 --- a/src/components/Link/LinkLogout.tsx +++ b/packages/web/src/components/Link/LinkLogout.tsx @@ -1,10 +1,8 @@ import { useCallback, useRef } from "react"; import { useHistory, useLocation } from "react-router-dom"; -import isAppAtom from "atoms/isApp"; -import { useRecoilValue } from "recoil"; - -import { sendAuthLogoutEventToFlutter } from "tools/sendEventToFlutter"; +import { deviceType } from "@/tools/loadenv"; +import { sendAuthLogoutEventToFlutter } from "@/tools/sendEventToFlutter"; type LinkLogoutProps = { children: React.ReactNode; @@ -14,17 +12,16 @@ type LinkLogoutProps = { export const useOnClickLogout = (redirect?: string) => { const history = useHistory(); const { pathname, search } = useLocation(); - const isApp = useRecoilValue(isAppAtom); const redirectPath = redirect || pathname + search; const isClicked = useRef(false); return useCallback(async () => { if (isClicked.current) return; isClicked.current = true; - if (isApp) await sendAuthLogoutEventToFlutter(); + if (deviceType.startsWith("app/")) await sendAuthLogoutEventToFlutter(); history.replace(`/logout?redirect=${encodeURIComponent(redirectPath)}`); isClicked.current = false; - }, [history, redirectPath, isApp]); + }, [history, redirectPath]); }; const LinkLogout = ({ children, redirect }: LinkLogoutProps) => { diff --git a/src/components/Link/LinkPayment.tsx b/packages/web/src/components/Link/LinkPayment.tsx similarity index 100% rename from src/components/Link/LinkPayment.tsx rename to packages/web/src/components/Link/LinkPayment.tsx diff --git a/src/components/Loading.tsx b/packages/web/src/components/Loading.tsx similarity index 79% rename from src/components/Loading.tsx rename to packages/web/src/components/Loading.tsx index deeee03dc..039c7a93c 100644 --- a/src/components/Loading.tsx +++ b/packages/web/src/components/Loading.tsx @@ -1,6 +1,6 @@ -import { css } from "@emotion/react"; +import { css, keyframes } from "@emotion/react"; -import theme, { Font } from "tools/theme"; +import theme, { Font } from "@/tools/theme"; type LoadingProps = { center?: boolean; @@ -18,9 +18,7 @@ const Loading = ({ ${font} color: ${color}; content: "Loading 🚕"; - animation: loading 1.5s linear infinite; - } - @keyframes loading { + animation: ${keyframes` 25% { content: "Loading. 🚕"; } @@ -29,7 +27,7 @@ const Loading = ({ } 75% { content: "Loading... 🚕"; - } + }`} 1.5s linear infinite; } `; const positionCenter = css` diff --git a/src/components/MiniCircle.tsx b/packages/web/src/components/MiniCircle.tsx similarity index 95% rename from src/components/MiniCircle.tsx rename to packages/web/src/components/MiniCircle.tsx index c2862328e..d496d70ae 100644 --- a/src/components/MiniCircle.tsx +++ b/packages/web/src/components/MiniCircle.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; type MiniCircleProps = { type: "from" | "to" | "date"; diff --git a/src/components/Modal/ModalElem.tsx b/packages/web/src/components/Modal/ModalElem.tsx similarity index 86% rename from src/components/Modal/ModalElem.tsx rename to packages/web/src/components/Modal/ModalElem.tsx index e8e7dc78a..f59210967 100644 --- a/src/components/Modal/ModalElem.tsx +++ b/packages/web/src/components/Modal/ModalElem.tsx @@ -8,13 +8,13 @@ import { useState, } from "react"; -import { useDelayBoolean } from "hooks/useDelay"; -import useDisableScrollEffect from "hooks/useDisableScrollEffect"; -import useKeyboardOperationEffect from "hooks/useKeyboardOperationEffect"; +import { useDelayBoolean } from "@/hooks/useDelay"; +import useDisableScrollEffect from "@/hooks/useDisableScrollEffect"; +import useKeyboardOperationEffect from "@/hooks/useKeyboardOperationEffect"; -import AdaptiveDiv from "components/AdaptiveDiv"; +import AdaptiveDiv from "@/components/AdaptiveDiv"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; import CloseRoundedIcon from "@mui/icons-material/CloseRounded"; @@ -25,6 +25,8 @@ export type ModalElemProps = { displayCloseBtn?: boolean; width?: PixelValue; padding?: Padding; + className?: string; + backgroundChildren?: ReactNode; children?: ReactNode; isAlert?: boolean; }; @@ -36,6 +38,8 @@ const ModalElem = ({ displayCloseBtn = true, width = theme.modal_width, padding = "0px", + className, + backgroundChildren, children, isAlert = false, }: ModalElemProps) => { @@ -111,8 +115,9 @@ const ModalElem = ({ if (!shouldMount) return null; return (
+ {backgroundChildren} -
+
{children} {displayCloseBtn && ( diff --git a/src/components/Modal/ModalProvider.tsx b/packages/web/src/components/Modal/ModalProvider.tsx similarity index 88% rename from src/components/Modal/ModalProvider.tsx rename to packages/web/src/components/Modal/ModalProvider.tsx index a74f8c2d6..ba1831ba1 100644 --- a/src/components/Modal/ModalProvider.tsx +++ b/packages/web/src/components/Modal/ModalProvider.tsx @@ -1,6 +1,6 @@ import ModalElem from "./ModalElem"; -import modalsAtom from "atoms/modals"; +import modalsAtom from "@/atoms/modals"; import { useRecoilValue } from "recoil"; const ModalProvider = () => { diff --git a/src/components/Modal/index.tsx b/packages/web/src/components/Modal/index.tsx similarity index 87% rename from src/components/Modal/index.tsx rename to packages/web/src/components/Modal/index.tsx index fd8085e41..32cfecef0 100644 --- a/src/components/Modal/index.tsx +++ b/packages/web/src/components/Modal/index.tsx @@ -1,10 +1,10 @@ import { useEffect } from "react"; -import useDateToken from "hooks/useDateToken"; +import useDateToken from "@/hooks/useDateToken"; import { ModalElemProps } from "./ModalElem"; -import modalsAtom from "atoms/modals"; +import modalsAtom from "@/atoms/modals"; import { useSetRecoilState } from "recoil"; const Modal = (props: ModalElemProps) => { diff --git a/src/components/ModalPopup/Body/BodyCallTaxi.tsx b/packages/web/src/components/ModalPopup/Body/BodyCallTaxi.tsx similarity index 83% rename from src/components/ModalPopup/Body/BodyCallTaxi.tsx rename to packages/web/src/components/ModalPopup/Body/BodyCallTaxi.tsx index 09cc760cf..206ed63a1 100644 --- a/src/components/ModalPopup/Body/BodyCallTaxi.tsx +++ b/packages/web/src/components/ModalPopup/Body/BodyCallTaxi.tsx @@ -1,13 +1,13 @@ -import ButtonShare from "components/Button/ButtonShare"; -import DottedLine from "components/DottedLine"; -import LinkCallTaxi from "components/Link/LinkCallTaxi"; +import ButtonShare from "@/components/Button/ButtonShare"; +import DottedLine from "@/components/DottedLine"; +import LinkCallTaxi from "@/components/Link/LinkCallTaxi"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; +import { ReactComponent as KakaoTaxiLogo } from "@/static/assets/serviceLogos/KakaoTaxiLogo.svg"; +import TmoneyOndaLogo from "@/static/assets/serviceLogos/TmoneyOndaLogo.png"; +import { ReactComponent as UTLogo } from "@/static/assets/serviceLogos/UTLogo.svg"; import LocationOnRoundedIcon from "@mui/icons-material/LocationOnRounded"; -import { ReactComponent as KakaoTaxiLogo } from "static/assets/KakaoTaxiLogo.svg"; -import TmoneyOndaLogo from "static/assets/TmoneyOndaLogo.png"; -import { ReactComponent as UTLogo } from "static/assets/UTLogo.svg"; export type BodyCallTaxiProps = { roomInfo: Room; diff --git a/src/components/ModalPopup/Body/BodyChatReportDone.tsx b/packages/web/src/components/ModalPopup/Body/BodyChatReportDone.tsx similarity index 78% rename from src/components/ModalPopup/Body/BodyChatReportDone.tsx rename to packages/web/src/components/ModalPopup/Body/BodyChatReportDone.tsx index e56667d70..b768e0308 100644 --- a/src/components/ModalPopup/Body/BodyChatReportDone.tsx +++ b/packages/web/src/components/ModalPopup/Body/BodyChatReportDone.tsx @@ -1,6 +1,6 @@ -import Button from "components/Button"; +import Button from "@/components/Button"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; type BodyChatReportDoneProps = { onChangeIsOpen?: (isOpen: boolean) => void; @@ -23,9 +23,11 @@ const BodyChatReportDone = ({ onChangeIsOpen }: BodyChatReportDoneProps) => {
+ +
+ + ); +}; + +export default BodyChatReportSelectUser; diff --git a/packages/web/src/components/ModalPopup/Body/BodyEvent2024SpringShare.tsx b/packages/web/src/components/ModalPopup/Body/BodyEvent2024SpringShare.tsx new file mode 100644 index 000000000..cf9e8a345 --- /dev/null +++ b/packages/web/src/components/ModalPopup/Body/BodyEvent2024SpringShare.tsx @@ -0,0 +1,111 @@ +import { useCallback, useEffect, useState } from "react"; +import QRCode from "react-qr-code"; + +import ButtonShare from "@/components/Button/ButtonShare"; +import DottedLine from "@/components/DottedLine"; +import LinkCopy from "@/components/Link/LinkCopy"; +import LinkKakaotalkShare from "@/components/Link/LinkKakaotalkShare"; + +import theme from "@/tools/theme"; + +import { ReactComponent as KakaoTalkLogo } from "@/static/assets/serviceLogos/KakaoTalkLogo.svg"; +import CheckIcon from "@mui/icons-material/Check"; +import ContentCopyIcon from "@mui/icons-material/ContentCopy"; + +export type BodyEvent2024SpringShareProps = { + inviteUrl: string; + height?: number; +}; + +const BodyEvent2024SpringShare = ({ + height, + inviteUrl, +}: BodyEvent2024SpringShareProps) => { + const { origin } = window.location; + + const [isCopied, setIsCopied] = useState(false); + const onCopy = useCallback(() => setIsCopied(true), [setIsCopied]); + + useEffect(() => { + if (isCopied) { + const timer = setTimeout(() => setIsCopied(false), 1000); + return () => clearTimeout(timer); + } + }, [isCopied]); + + const styleWrapper = height + ? { + height, + display: "flex", + flexDirection: "column" as any, + } + : {}; + + const styleGuide = { + ...theme.font12, + color: theme.gray_text, + margin: "0 8px 12px", + }; + const styleQRSection = { + marginTop: "12px", + position: "relative" as any, + overflow: "hidden", + textAlign: "center" as any, + }; + const styleButtonSection = { + display: "flex", + justifyContent: "center", + gap: "10px", + margin: "12px 0px 0", + }; + + return ( +
+
+ 이벤트를 여러 사람들에게 공유할 수 있습니다. 이 링크를 통해 다른 + 사용자가 이벤트에 참여하면, 회원님과 새 참여자 모두{" "} + 50 넙죽코인을 획득합니다. +
+ +
+
+ +
+
+
+ + } + background="#FFE812" + /> + + + + ) : ( + + ) + } + background={theme.gray_background} + /> + +
+
+ ); +}; + +export default BodyEvent2024SpringShare; diff --git a/src/components/ModalPopup/Body/BodyNotificationGuide.tsx b/packages/web/src/components/ModalPopup/Body/BodyNotificationGuide.tsx similarity index 90% rename from src/components/ModalPopup/Body/BodyNotificationGuide.tsx rename to packages/web/src/components/ModalPopup/Body/BodyNotificationGuide.tsx index cfe35298c..6ed91e8cb 100644 --- a/src/components/ModalPopup/Body/BodyNotificationGuide.tsx +++ b/packages/web/src/components/ModalPopup/Body/BodyNotificationGuide.tsx @@ -1,9 +1,7 @@ -import { useValueRecoilState } from "hooks/useFetchRecoilState"; - -import theme from "tools/theme"; +import { deviceType } from "@/tools/loadenv"; +import theme from "@/tools/theme"; const BodyNotificationGuide = () => { - const { deviceType } = useValueRecoilState("loginInfo") || {}; const { protocol, host } = window.location; const styleGuide = { @@ -12,7 +10,15 @@ const BodyNotificationGuide = () => { marginBottom: "12px", wordBreak: "keep-all" as any, }; - return deviceType === "web" ? ( + return deviceType.startsWith("app/") ? ( + <> +
알림 기능을 사용할 수 없습니다.
+
+ 앱 푸시 알림 기능을 활성화하기 위해서는, 디바이스 환경설정의 알림 권한 + 허용이 필요합니다. +
+ + ) : ( <>
알림 기능을 사용할 수 없습니다.
@@ -44,14 +50,6 @@ const BodyNotificationGuide = () => { 브라우저일 수 있습니다.
- ) : ( - <> -
알림 기능을 사용할 수 없습니다.
-
- 앱 푸시 알림 기능을 활성화하기 위해서는, 디바이스 환경설정의 알림 권한 - 허용이 필요합니다. -
- ); }; diff --git a/src/components/ModalPopup/Body/BodyPrivacyPolicy.tsx b/packages/web/src/components/ModalPopup/Body/BodyPrivacyPolicy.tsx similarity index 89% rename from src/components/ModalPopup/Body/BodyPrivacyPolicy.tsx rename to packages/web/src/components/ModalPopup/Body/BodyPrivacyPolicy.tsx index 4fb0e91c2..6c3ea5570 100644 --- a/src/components/ModalPopup/Body/BodyPrivacyPolicy.tsx +++ b/packages/web/src/components/ModalPopup/Body/BodyPrivacyPolicy.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; const BodyPrivacyPolicy = () => { const { protocol, host } = window.location; @@ -28,7 +28,7 @@ const BodyPrivacyPolicy = () => { "Taxi")은(는) 「개인정보 보호법」 제30조에 따라 정보주체의 개인정보를 보호하고 이와 관련한 고충을 신속하고 원활하게 처리할 수 있도록 하기 위하여 다음과 같이 개인정보 처리방침을 수립·공개합니다. -
○ 이 개인정보처리방침은 2023년 3월 4부터 적용됩니다. +
○ 이 개인정보처리방침은 2024년 2월 17부터 적용됩니다.
제1조(개인정보의 처리 목적)
@@ -44,6 +44,15 @@ const BodyPrivacyPolicy = () => { 회원 가입의사 확인, 회원제 서비스 제공에 따른 본인 식별·인증, 회원자격 유지·관리, 서비스 부정이용 방지 목적으로 개인정보를 처리합니다.
+
+ 2. 이벤트 관리 및 상품 전달 +
+ 이벤트 응모 및 경품 추첨 과정에서 해당 이벤트의 참여자에 한해 추가 + 개인정보 수집이 발생할 수 있습니다. 추가로 개인정보를 수집할 경우에는 + 해당 개인정보 수집 시점에서 이용자에게 '수집하는 개인정보 항목', + '개인정보의 수집 및 이용목적', '개인정보의 보유 및 이용기간'에 대해 + 안내하고 동의를 받겠습니다. +
제2조(개인정보의 처리 및 보유 기간)
① < SPARCS >은(는) 법령에 따른 개인정보 보유·이용기간 또는 @@ -56,13 +65,21 @@ const BodyPrivacyPolicy = () => {
- <홈페이지 회원가입 및 관리>와 관련한 개인정보는 수집.이용에 관한 동의일로부터<1년>까지 위 이용목적을 위하여 보유.이용됩니다. -
- 보유근거 : 회원가입 및 탈퇴 의사의 확인, 회원 식별 +
- 보유근거 : 회원가입 및 탈퇴 의사의 확인, 회원 식별, 서비스 + 부정이용자 연락 +
+ - 2.<이벤트 관리 및 상품 전달> +
+ - <이벤트 관리 및 상품 전달>와 관련한 개인정보는 수집.이용에 관한 + 동의일로부터<1년>까지 위 이용목적을 위하여 보유.이용됩니다. +
- 보유근거 : 이벤트 참여 의사 확인, 참여자 식별, 상품 전달, 이벤트 + 부정 참여자 연락
제3조(처리하는 개인정보의 항목)
① < SPARCS >은(는) 다음의 개인정보 항목을 처리하고 있습니다.
- 1< 홈페이지 회원가입 및 관리 > -
- 필수항목 : 이메일, 이름, 접속 로그, 접속 IP 정보, 학번 +
- 필수항목 : 이메일, 이름, 접속 로그, 접속 IP 정보, 학번, 전화번호
제4조(개인정보의 파기절차 및 파기방법)
@@ -177,8 +194,8 @@ const BodyPrivacyPolicy = () => { 피해구제 등을 위하여 아래와 같이 개인정보 보호책임자를 지정하고 있습니다.
- - 성명 :김건 -
- 연락처 :010-5633-6757, geon6757@kaist.ac.kr + - 성명 : 김민찬 +
- 연락처 : 010-8608-7057, kmc7468@kaist.ac.kr
② 정보주체께서는 < SPARCS >("Taxi") 의 서비스를 이용하시면서 발생한 모든 개인정보 보호 관련 문의, 불만처리, 피해구제 등에 관한 사항을 개인정보 보호책임자 및 담당부서로 문의하실 수 있습니다. @@ -196,8 +213,8 @@ const BodyPrivacyPolicy = () => { 신속하게 처리되도록 노력하겠습니다.
- - 담당자 : 김건 -
- 연락처 : 010-5633-6757, geon6757@kaist.ac.kr + - 담당자 : 김민찬 +
- 연락처 : 010-8608-7057, kmc7468@kaist.ac.kr
제10조(정보주체의 권익침해에 대한 구제방법) @@ -225,7 +242,7 @@ const BodyPrivacyPolicy = () => {
제11조(개인정보 처리방침 변경)
- 이 개인정보처리방침은 2023년 3월 4부터 적용됩니다. + 이 개인정보처리방침은 2024년 2월 17부터 적용됩니다.
); diff --git a/src/components/ModalPopup/Body/BodyReport.tsx b/packages/web/src/components/ModalPopup/Body/BodyReport.tsx similarity index 93% rename from src/components/ModalPopup/Body/BodyReport.tsx rename to packages/web/src/components/ModalPopup/Body/BodyReport.tsx index 7c9c7793f..6097f52ed 100644 --- a/src/components/ModalPopup/Body/BodyReport.tsx +++ b/packages/web/src/components/ModalPopup/Body/BodyReport.tsx @@ -1,10 +1,10 @@ import { useTranslation } from "react-i18next"; -import DottedLine from "components/DottedLine"; -import Empty from "components/Empty"; +import DottedLine from "@/components/DottedLine"; +import Empty from "@/components/Empty"; -import { date2str } from "tools/moment"; -import theme from "tools/theme"; +import { date2str } from "@/tools/moment"; +import theme from "@/tools/theme"; type BodyReportProps = { option: "Reporting" | "Reported"; @@ -54,7 +54,7 @@ const ReportList = (props: BodyReportProps) => { }; if (!props.selectedReportHistory?.length) { return ( - + {props.option === "Reporting" ? t("page_report.empty_reported") : t("page_report.empty_received")} diff --git a/src/components/ModalPopup/Body/BodyRoomSelection.tsx b/packages/web/src/components/ModalPopup/Body/BodyRoomSelection.tsx similarity index 85% rename from src/components/ModalPopup/Body/BodyRoomSelection.tsx rename to packages/web/src/components/ModalPopup/Body/BodyRoomSelection.tsx index 57707fc92..306f1a2cc 100644 --- a/src/components/ModalPopup/Body/BodyRoomSelection.tsx +++ b/packages/web/src/components/ModalPopup/Body/BodyRoomSelection.tsx @@ -4,25 +4,26 @@ import { useHistory } from "react-router-dom"; import { useFetchRecoilState, + useIsLogin, useValueRecoilState, -} from "hooks/useFetchRecoilState"; -import useIsTimeOver from "hooks/useIsTimeOver"; -import { useAxios } from "hooks/useTaxiAPI"; +} from "@/hooks/useFetchRecoilState"; +import useIsTimeOver from "@/hooks/useIsTimeOver"; +import { useAxios } from "@/hooks/useTaxiAPI"; -import Button from "components/Button"; -import DottedLine from "components/DottedLine"; -import LinkLogin from "components/Link/LinkLogin"; -import MiniCircle from "components/MiniCircle"; -import Users from "components/User/Users"; -import { MAX_PARTICIPATION } from "pages/Myroom"; +import Button from "@/components/Button"; +import DottedLine from "@/components/DottedLine"; +import LinkLogin from "@/components/Link/LinkLogin"; +import MiniCircle from "@/components/MiniCircle"; +import Users from "@/components/User/Users"; +import { MAX_PARTICIPATION } from "@/pages/Myroom"; -import alertAtom from "atoms/alert"; +import alertAtom from "@/atoms/alert"; import { useSetRecoilState } from "recoil"; -import { dayServerToClient } from "tools/day"; -import { date2str } from "tools/moment"; -import theme from "tools/theme"; -import { getLocationName } from "tools/trans"; +import { dayServerToClient } from "@/tools/day"; +import { date2str } from "@/tools/moment"; +import theme from "@/tools/theme"; +import { getLocationName } from "@/tools/trans"; import ArrowRightAltRoundedIcon from "@mui/icons-material/ArrowRightAltRounded"; @@ -110,7 +111,7 @@ const BodyRoomSelection = ({ roomInfo }: BodyRoomSelectionProps) => { const fetchMyRooms = useFetchRecoilState("myRooms"); const setAlert = useSetRecoilState(alertAtom); - const isLogin = !!loginInfo?.id; // 로그인 여부 + const isLogin = useIsLogin() && !!loginInfo?.id; // 로그인 여부 const isRoomFull = roomInfo && roomInfo.part.length >= roomInfo.maxPartLength; // 방이 꽉 찼는지 여부 const isAlreadyPart = isLogin && @@ -204,9 +205,11 @@ const BodyRoomSelection = ({ roomInfo }: BodyRoomSelectionProps) => { diff --git a/src/components/ModalPopup/Body/BodyRoomShare.tsx b/packages/web/src/components/ModalPopup/Body/BodyRoomShare.tsx similarity index 86% rename from src/components/ModalPopup/Body/BodyRoomShare.tsx rename to packages/web/src/components/ModalPopup/Body/BodyRoomShare.tsx index 82d55ca7d..589417c22 100644 --- a/src/components/ModalPopup/Body/BodyRoomShare.tsx +++ b/packages/web/src/components/ModalPopup/Body/BodyRoomShare.tsx @@ -2,19 +2,19 @@ import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import QRCode from "react-qr-code"; -import ButtonShare from "components/Button/ButtonShare"; -import DottedLine from "components/DottedLine"; -import LinkCopy from "components/Link/LinkCopy"; -import LinkKakaotalkShare from "components/Link/LinkKakaotalkShare"; +import ButtonShare from "@/components/Button/ButtonShare"; +import DottedLine from "@/components/DottedLine"; +import LinkCopy from "@/components/Link/LinkCopy"; +import LinkKakaotalkShare from "@/components/Link/LinkKakaotalkShare"; -import { date2str } from "tools/moment"; -import theme from "tools/theme"; -import { getLocationName } from "tools/trans"; +import { ogServer } from "@/tools/loadenv"; +import { date2str } from "@/tools/moment"; +import theme from "@/tools/theme"; +import { getLocationName } from "@/tools/trans"; +import { ReactComponent as KakaoTalkLogo } from "@/static/assets/serviceLogos/KakaoTalkLogo.svg"; import CheckIcon from "@mui/icons-material/Check"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; -import { ogServer } from "loadenv"; -import { ReactComponent as KakaoTalkLogo } from "static/assets/KakaoTalkLogo.svg"; export type BodyRoomShareProps = { roomInfo: Room; diff --git a/src/components/ModalPopup/Body/BodyTerms.tsx b/packages/web/src/components/ModalPopup/Body/BodyTerms.tsx similarity index 99% rename from src/components/ModalPopup/Body/BodyTerms.tsx rename to packages/web/src/components/ModalPopup/Body/BodyTerms.tsx index 9b678381c..3ff587531 100644 --- a/src/components/ModalPopup/Body/BodyTerms.tsx +++ b/packages/web/src/components/ModalPopup/Body/BodyTerms.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; const BodyTerms = () => { const styleBox = { diff --git a/src/components/ModalPopup/Body/BodyTermsPrivacy.tsx b/packages/web/src/components/ModalPopup/Body/BodyTermsPrivacy.tsx similarity index 98% rename from src/components/ModalPopup/Body/BodyTermsPrivacy.tsx rename to packages/web/src/components/ModalPopup/Body/BodyTermsPrivacy.tsx index c04244c16..65b66ab48 100644 --- a/src/components/ModalPopup/Body/BodyTermsPrivacy.tsx +++ b/packages/web/src/components/ModalPopup/Body/BodyTermsPrivacy.tsx @@ -1,6 +1,6 @@ import { Link } from "react-router-dom"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; const BodyTermsPrivacy = () => { const styleBox = { diff --git a/src/components/ModalPopup/ModalCallTaxi.tsx b/packages/web/src/components/ModalPopup/ModalCallTaxi.tsx similarity index 91% rename from src/components/ModalPopup/ModalCallTaxi.tsx rename to packages/web/src/components/ModalPopup/ModalCallTaxi.tsx index 97ed443be..af8dc0030 100644 --- a/src/components/ModalPopup/ModalCallTaxi.tsx +++ b/packages/web/src/components/ModalPopup/ModalCallTaxi.tsx @@ -1,8 +1,8 @@ -import Modal from "components/Modal"; +import Modal from "@/components/Modal"; import BodyCallTaxi from "./Body/BodyCallTaxi"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; import LocalTaxiRoundedIcon from "@mui/icons-material/LocalTaxiRounded"; diff --git a/src/components/ModalPopup/ModalChatCancel.tsx b/packages/web/src/components/ModalPopup/ModalChatCancel.tsx similarity index 79% rename from src/components/ModalPopup/ModalChatCancel.tsx rename to packages/web/src/components/ModalPopup/ModalChatCancel.tsx index ffdecb0ff..73fefad72 100644 --- a/src/components/ModalPopup/ModalChatCancel.tsx +++ b/packages/web/src/components/ModalPopup/ModalChatCancel.tsx @@ -1,16 +1,16 @@ import { useCallback } from "react"; import { useHistory } from "react-router-dom"; -import { useFetchRecoilState } from "hooks/useFetchRecoilState"; -import { useAxios } from "hooks/useTaxiAPI"; +import { useFetchRecoilState } from "@/hooks/useFetchRecoilState"; +import { useAxios } from "@/hooks/useTaxiAPI"; -import Button from "components/Button"; -import Modal from "components/Modal"; +import Button from "@/components/Button"; +import Modal from "@/components/Modal"; -import alertAtom from "atoms/alert"; +import alertAtom from "@/atoms/alert"; import { useSetRecoilState } from "recoil"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; type ModalChatCancelProps = Omit< Parameters[0], @@ -87,20 +87,24 @@ const ModalChatCancel = ({ roomId, ...modalProps }: ModalChatCancelProps) => { > + +
+ + ); +}; + +export default ModalEvent2023FallItem; diff --git a/packages/web/src/components/ModalPopup/ModalEvent2023FallItemInstagram.tsx b/packages/web/src/components/ModalPopup/ModalEvent2023FallItemInstagram.tsx new file mode 100644 index 000000000..590e71da6 --- /dev/null +++ b/packages/web/src/components/ModalPopup/ModalEvent2023FallItemInstagram.tsx @@ -0,0 +1,158 @@ +import { EventItem } from "@/types/event2023fall"; + +import Button from "@/components/Button"; +import DottedLine from "@/components/DottedLine"; +import LinkEvent2023FallInstagramStoryShare from "@/components/Link/LinkEvent2023FallInstagramStoryShare"; +import Modal from "@/components/Modal"; + +import theme from "@/tools/theme"; + +import ShareRoundedIcon from "@mui/icons-material/ShareRounded"; + +const backgroundLayerUrl = + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/instagram_background_item.png"; +const stickerLayerDefaultUrl = + "https://sparcs-taxi-prod.s3.ap-northeast-2.amazonaws.com/assets/event-2023fall/instagram_sticker.png"; + +type ModalEvent2023FallItemInstagramProps = { item?: EventItem } & Parameters< + typeof Modal +>[0]; + +const Background = () => ( +
+
+
+
+); + +const ModalEvent2023FallItemInstagram = ({ + item, + ...modalProps +}: ModalEvent2023FallItemInstagramProps) => { + const stickerLayerUrl = + item?.instagramStoryStickerImageUrl || stickerLayerDefaultUrl; + console.log(modalProps.isOpen, stickerLayerUrl); + + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px 12px", + }; + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", + }; + const styleGuide = { + ...theme.font12, + color: theme.gray_text, + margin: "0 8px 12px", + }; + + return ( + } + {...modalProps} + > +
+ + 인스타그램 스토리에 공유하기 +
+
+ 상품 획득을 축하합니다! 이벤트를 + 열심히 즐긴 당신. 그 상품 획득을 축하 받을 자격이 충분합니다. 인스타그램 + 스토리에 상품 획득을 공유하세요. + + 인스타그램 스토리에 공유하면, 송편 100개를 획득할 수 있는 퀘스트를 + 달성할 수 있습니다. + +
+ +
+ + + @sparcs.kaist + + {" "} + 태그 부탁드려요 :D +
+
+ background + sticker +
+
+ + + + +
+ + ); +}; + +export default ModalEvent2023FallItemInstagram; diff --git a/packages/web/src/components/ModalPopup/ModalEvent2023FallJoin.tsx b/packages/web/src/components/ModalPopup/ModalEvent2023FallJoin.tsx new file mode 100644 index 000000000..a1ce120a0 --- /dev/null +++ b/packages/web/src/components/ModalPopup/ModalEvent2023FallJoin.tsx @@ -0,0 +1,192 @@ +import { useCallback, useMemo, useState } from "react"; + +import { useEvent2023FallQuestComplete } from "@/hooks/event/useEvent2023FallQuestComplete"; +import { + useFetchRecoilState, + useIsLogin, + useValueRecoilState, +} from "@/hooks/useFetchRecoilState"; +import { useAxios } from "@/hooks/useTaxiAPI"; + +import Button from "@/components/Button"; +import DottedLine from "@/components/DottedLine"; +import Input from "@/components/Input"; +import Modal from "@/components/Modal"; + +import alertAtom from "@/atoms/alert"; +import { useSetRecoilState } from "recoil"; + +import regExpTest from "@/tools/regExpTest"; +import theme from "@/tools/theme"; + +import FestivalRoundedIcon from "@mui/icons-material/FestivalRounded"; + +type ModalEvent2023FallJoinProps = Parameters[0]; + +const ModalEvent2023FallJoin = (modalProps: ModalEvent2023FallJoinProps) => { + const axios = useAxios(); + const setAlert = useSetRecoilState(alertAtom); + const isLogin = useIsLogin(); + const { phoneNumber: phoneNumberFromLoginInfo } = + useValueRecoilState("loginInfo") || {}; + const { isAgreeOnTermsOfEvent } = + useValueRecoilState("event2023FallInfo") || {}; + const fetchLoginInfo = useFetchRecoilState("loginInfo"); + //#region event2023Fall + const event2023FallQuestComplete = useEvent2023FallQuestComplete(); + //#endregion + + const [phoneNumber, setPhoneNumber] = useState(""); + const isValidPhoneNumber = useMemo( + () => regExpTest.phoneNumber(phoneNumber), + [phoneNumber] + ); + + const onClickJoin = useCallback( + () => + axios({ + url: "/events/2023fall/global-state/create", + method: "post", + data: { phoneNumber }, + onSuccess: () => { + fetchLoginInfo(); + //#region event2023Fall + event2023FallQuestComplete("firstLogin"); + //#endregion + modalProps.onChangeIsOpen?.(false); + }, + onError: () => setAlert("이벤트 참여에 실패하였습니다."), + }), + [phoneNumber, setPhoneNumber, event2023FallQuestComplete] + ); + + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px 12px", + }; + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", + }; + const styleText = { + ...theme.font12, + color: theme.gray_text, + margin: "0 8px", + }; + const styleInputWrap = { + margin: "12px 8px", + display: "flex", + alignItems: "center", + color: theme.gray_text, + whiteSpace: "nowrap", + ...theme.font14, + } as const; + + return ( + +
+ + 한가위 송편 이벤트 +
+
+ • 택시 동승을 하지 않는 사용자는{" "} + + 택시 출발 시각이 지나기 전에 탑승 취소 + + 를 하여 방에서 나가야 합니다. +
+
+
+ • 실제 Taxi 동승을 하지 않고{" "} + 허위로 방을 개설하거나 참여하여 + 이벤트 퀘스트를 달성하는 것은{" "} + 부정 이용에 해당됩니다. Taxi 서비스 + 이용 중 서비스를 부정 이용하였다고 판단되거나, 신고를 받은 사용자에게는 + 사안에 따라{" "} + + 이벤트 상품이 지급되지 않을 수 있습니다. + {" "} + 위 경우, SPARCS Taxi팀 서비스 관리자는 서비스 부정 이용을 방지하기 위해 + 택시 탑승을 인증할 수 있는{" "} + 영수증 또는 카카오T 이용기록을 + 요청할 수 있습니다. 또한, 본 서비스를 부정 이용하는 사용자에게는 택시 + 서비스 이용 제한 및 법적 조치를 취할 수 있습니다. +
+
+
+ •{" "} + + 입력해주신 연락처로 이벤트 상품을 전달해드립니다. + {" "} + 또한, 서비스 신고 대응 및 본인 확인을 위해 사용될 수 있습니다.{" "} + + 입력해주신 연락처는 이후 수정이 불가능합니다. + +
+
+
+ • 본 약관은 동의 이후에도 {'"'}마이페이지{">"}한가위 송편 이벤트 참여 + 약관{'"'}에서 다시 확인하실 수 있습니다.{" "} +
+ {isLogin && + (isAgreeOnTermsOfEvent ? ( + <> +
+ +
+ 전화번호 + +
+ + + ) : ( + <> +
+ +
+ 전화번호 + +
+ + + ))} + + ); +}; + +export default ModalEvent2023FallJoin; diff --git a/packages/web/src/components/ModalPopup/ModalEvent2023FallRandomBox.tsx b/packages/web/src/components/ModalPopup/ModalEvent2023FallRandomBox.tsx new file mode 100644 index 000000000..d1d855625 --- /dev/null +++ b/packages/web/src/components/ModalPopup/ModalEvent2023FallRandomBox.tsx @@ -0,0 +1,131 @@ +import { + Dispatch, + SetStateAction, + memo, + useCallback, + useEffect, + useState, +} from "react"; + +import type { EventItem } from "@/types/event2023fall"; + +import { useDelay, useDelayBoolean } from "@/hooks/useDelay"; + +import Button from "@/components/Button"; +import DottedLine from "@/components/DottedLine"; +import BodyRandomBox from "@/components/Event/BodyRandomBox"; +import Loading from "@/components/Loading"; +import Modal from "@/components/Modal"; + +import "./ModalEvent2023FallRandomBoxBackground.css"; + +import theme from "@/tools/theme"; + +import HelpCenterRoundedIcon from "@mui/icons-material/HelpCenterRounded"; + +const Background = () => ( +
+
+
+
+); + +type ModalEvent2023FallRandomBoxProps = { + item?: EventItem; + setShareItem?: Dispatch>>; +} & Parameters[0]; + +const ModalEvent2023FallRandomBox = ({ + item, + setShareItem, + ...modalProps +}: ModalEvent2023FallRandomBoxProps) => { + const [isBoxOpend, setIsBoxOpend] = useState(false); + const isDisplayRandomBox = !useDelayBoolean(!modalProps.isOpen, 500); + const isDisplayItemName = useDelay(isBoxOpend, !isBoxOpend, 6000); + const onClickOk = useCallback(() => setIsBoxOpend(true), []); + + const onChangeIsOpen = useCallback( + (isOpen: boolean) => { + if (!isOpen && item) setShareItem?.(item); + modalProps?.onChangeIsOpen?.(isOpen); + }, + [item, setShareItem, modalProps] + ); + + useEffect(() => { + if (!modalProps.isOpen) setIsBoxOpend(false); + }, [modalProps.isOpen]); + + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px 12px", + }; + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", + }; + const styleText = { + ...theme.font12, + color: theme.gray_text, + margin: "0 8px 12px", + }; + + return ( + : undefined} + onEnter={onClickOk} + onChangeIsOpen={onChangeIsOpen} + > +
+ + 랜덤박스 열기 +
+
+ 랜덤박스를 획득했어요. 상자{" "} + 또는 열기 버튼을 눌러 상자 안 상품을 확인해세요! +
+ + {isDisplayRandomBox ? ( + + ) : ( +
+ +
+ )} + {isDisplayItemName && ( +
+ 축하합니다! 랜덤박스에서{" "} + + {'"'} + {item?.name || ""} + {'"'} + + 을(를) 획득하였습니다 +
+ )} + +
+ ); +}; + +export default memo(ModalEvent2023FallRandomBox); diff --git a/packages/web/src/components/ModalPopup/ModalEvent2023FallRandomBoxBackground.css b/packages/web/src/components/ModalPopup/ModalEvent2023FallRandomBoxBackground.css new file mode 100644 index 000000000..ab929ed6d --- /dev/null +++ b/packages/web/src/components/ModalPopup/ModalEvent2023FallRandomBoxBackground.css @@ -0,0 +1,98 @@ +.c2023fallevent-before, +.c2023fallevent-after { + position: absolute; + width: 8px; + height: 8px; + border-radius: 50%; + box-shadow: -120px -218.66667px blue, 248px -16.66667px #00ff84, + 190px 16.33333px #002bff, -113px -308.66667px #ff009d, + -109px -287.66667px #ffb300, -50px -313.66667px #ff006e, + 226px -31.66667px #ff4000, 180px -351.66667px #ff00d0, + -12px -338.66667px #00f6ff, 220px -388.66667px #99ff00, + -69px -27.66667px #ff0400, -111px -339.66667px #6200ff, + 155px -237.66667px #00ddff, -152px -380.66667px #00ffd0, + -50px -37.66667px #00ffdd, -95px -175.66667px #a6ff00, + -88px 10.33333px #0d00ff, 112px -309.66667px #005eff, + 69px -415.66667px #ff00a6, 168px -100.66667px #ff004c, + -244px 24.33333px #ff6600, 97px -325.66667px #ff0066, + -211px -182.66667px #00ffa2, 236px -126.66667px #b700ff, + 140px -196.66667px #9000ff, 125px -175.66667px #00bbff, + 118px -381.66667px #ff002f, 144px -111.66667px #ffae00, + 36px -78.66667px #f600ff, -63px -196.66667px #c800ff, + -218px -227.66667px #d4ff00, -134px -377.66667px #ea00ff, + -36px -412.66667px #ff00d4, 209px -106.66667px #00fff2, + 91px -278.66667px #000dff, -22px -191.66667px #9dff00, + 139px -392.66667px #a6ff00, 56px -2.66667px #0099ff, + -156px -276.66667px #ea00ff, -163px -233.66667px #00fffb, + -238px -346.66667px #00ff73, 62px -363.66667px #0088ff, + 244px -170.66667px #0062ff, 224px -142.66667px #b300ff, + 141px -208.66667px #9000ff, 211px -285.66667px #ff6600, + 181px -128.66667px #1e00ff, 90px -123.66667px #c800ff, + 189px 70.33333px #00ffc8, -18px -383.66667px #00ff33, + 100px -6.66667px #ff008c; + animation: 2s c2023fallevent-bang ease-out infinite backwards, + 1s c2023fallevent-gravity ease-in infinite backwards, + 5s c2023fallevent-position linear infinite backwards; +} + +.c2023fallevent-after { + animation-delay: 2s, 3s, 15s; + animation-duration: 2s, 3s, 15s; +} + +@keyframes c2023fallevent-bang { + from { + box-shadow: 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, + 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, + 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, + 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, + 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, + 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, + 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, + 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, 0 0 white, + 0 0 white, 0 0 white, 0 0 white; + } +} + +@keyframes c2023fallevent-gravity { + from { + transform: translateY(0); + opacity: 1; + } + to { + transform: translateY(500px); + opacity: 0; + } +} + +@keyframes c2023fallevent-position { + 0%, + 19.9% { + margin-top: 10%; + margin-left: 40%; + } + + 20%, + 39.9% { + margin-top: 40%; + margin-left: 30%; + } + + 40%, + 59.9% { + margin-top: 20%; + margin-left: 70%; + } + + 60%, + 79.9% { + margin-top: 30%; + margin-left: 20%; + } + + 80%, + 99.9% { + margin-top: 30%; + margin-left: 80%; + } +} diff --git a/packages/web/src/components/ModalPopup/ModalEvent2024SpringAbuseWarning.tsx b/packages/web/src/components/ModalPopup/ModalEvent2024SpringAbuseWarning.tsx new file mode 100644 index 000000000..56f1112eb --- /dev/null +++ b/packages/web/src/components/ModalPopup/ModalEvent2024SpringAbuseWarning.tsx @@ -0,0 +1,39 @@ +import Modal from "@/components/Modal"; + +import BodyEvent2024AbuseWarning from "./Body/BodyEvent2024AbuseWarning"; + +import theme from "@/tools/theme"; + +import ReportGmailerrorredRoundedIcon from "@mui/icons-material/ReportGmailerrorredRounded"; + +type ModalEvent2024AbuseWarningProps = Omit< + Parameters[0], + "padding" | "children" | "onEnter" +>; + +const ModalChatReport = ({ + ...modalProps +}: ModalEvent2024AbuseWarningProps) => { + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px 12px", + }; + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", + }; + + return ( + +
+ + 경고 +
+ +
+ ); +}; + +export default ModalChatReport; diff --git a/packages/web/src/components/ModalPopup/ModalEvent2024SpringJoin.tsx b/packages/web/src/components/ModalPopup/ModalEvent2024SpringJoin.tsx new file mode 100644 index 000000000..e7771dd13 --- /dev/null +++ b/packages/web/src/components/ModalPopup/ModalEvent2024SpringJoin.tsx @@ -0,0 +1,317 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; + +import { useEvent2024SpringQuestComplete } from "@/hooks/event/useEvent2024SpringQuestComplete"; +import { + useFetchRecoilState, + useIsLogin, + useValueRecoilState, +} from "@/hooks/useFetchRecoilState"; +import { useAxios } from "@/hooks/useTaxiAPI"; + +import Button from "@/components/Button"; +import DottedLine from "@/components/DottedLine"; +import Input from "@/components/Input"; +import Modal from "@/components/Modal"; + +import LinkLogin from "../Link/LinkLogin"; +import ProfileImage from "../User/ProfileImage"; + +import alertAtom from "@/atoms/alert"; +import { useSetRecoilState } from "recoil"; + +import regExpTest from "@/tools/regExpTest"; +import theme from "@/tools/theme"; + +import FestivalRoundedIcon from "@mui/icons-material/FestivalRounded"; + +type ModalEvent2024SpringJoinProps = Parameters[0] & { + inviterId?: string; +}; + +const ModalEvent2024SpringJoin = ({ + inviterId, + ...modalProps +}: ModalEvent2024SpringJoinProps) => { + const axios = useAxios(); + const setAlert = useSetRecoilState(alertAtom); + const isLogin = useIsLogin(); + const { phoneNumber: phoneNumberFromLoginInfo } = + useValueRecoilState("loginInfo") || {}; + const { + isAgreeOnTermsOfEvent, + isEligible, + group: groupFromLoginInfo, + } = useValueRecoilState("event2024SpringInfo") || {}; + const fetchLoginInfo = useFetchRecoilState("loginInfo"); + //#region event2024Spring + const event2024SpringQuestComplete = useEvent2024SpringQuestComplete(); + //#endregion + + const [phoneNumber, setPhoneNumber] = useState(""); + const [group, setGroup] = useState(0); + + const [inviterInfo, setInvitorInfo] = useState<{ + profileImageUrl: string; + nickname: string; + }>(); + + const getInvitorInfo = useCallback( + () => + axios({ + url: `/events/2024spring/invite/search/${inviterId}`, + method: "get", + onSuccess: (data) => { + setInvitorInfo(data); + }, + onError: () => setAlert("올바르지 않은 추천인입니다."), + }), + [inviterId] + ); + + const isInvited = !!inviterId; + + useEffect(() => { + if (!isAgreeOnTermsOfEvent && isInvited) getInvitorInfo(); + }, [inviterId]); + + const isValidPhoneNumber = useMemo( + () => regExpTest.phoneNumber(phoneNumber), + [phoneNumber] + ); + const isValidGroup = useMemo(() => group > 0 && group < 27, [group]); + + const onClickJoin = useCallback( + () => + axios({ + url: "/events/2024spring/globalState/create", + method: "post", + data: { phoneNumber, group, inviter: inviterId }, + onSuccess: () => { + fetchLoginInfo(); + //#region event2024Spring + event2024SpringQuestComplete("firstLogin"); + //#endregion + modalProps.onChangeIsOpen?.(false); + }, + onError: () => setAlert("이벤트 참여에 실패하였습니다."), + }), + [phoneNumber, setPhoneNumber, group, setGroup, event2024SpringQuestComplete] + ); + + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px 12px", + }; + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", + }; + const styleText = { + ...theme.font12, + color: theme.gray_text, + margin: "0 8px", + }; + const styleInputWrap = { + margin: "0 8px 12px", + display: "flex", + alignItems: "center", + color: theme.gray_text, + whiteSpace: "nowrap", + ...theme.font14, + } as const; + + return ( + +
+ + 새터반 택시대제전 이벤트 +
+
+ • 택시 동승을 하지 않는 사용자는{" "} + + 택시 출발 시각이 지나기 전에 탑승 취소 + + 를 하여 방에서 나가야 합니다. +
+
+
+ • 실제 Taxi 동승을 하지 않고{" "} + 허위로 방을 개설하거나 참여하여 + 이벤트 퀘스트를 달성하는 것은{" "} + 부정 이용에 해당됩니다. Taxi 서비스 + 이용 중 서비스를 부정 이용하였다고 판단되거나, 신고를 받은 사용자에게는 + 사안에 따라{" "} + + 이벤트 상품이 지급되지 않을 수 있습니다. + {" "} + 위 경우, SPARCS Taxi팀 서비스 관리자는 서비스 부정 이용을 방지하기 위해 + 택시 탑승을 인증할 수 있는{" "} + 영수증 또는 카카오T 이용기록을 + 요청할 수 있습니다. 또한, 본 서비스를 부정 이용하는 사용자에게는 택시 + 서비스 이용 제한 및 법적 조치를 취할 수 있습니다. +
+
+
+ •{" "} + + 입력해주신 새터반으로 점수가 합산됩니다. + {" "} + 또한, 입력해주신 연락처는 서비스 신고 대응 및 본인 확인을 위해 사용될 수 + 있습니다. +
+
+
+ •{" "} + + 입력해주신 연락처와 새터반은 이후 수정이 불가능합니다. + {" "} +
+
+
+ •{" "} + + 추천인 이벤트 참여를 위해서는 추천인이 발송한 링크로 이벤트에 참여해야 + 합니다. + {" "} + 추천인을 통해 이벤트에 참여할 시, 참가자와 추천인 모두에게 50 넙죽코인이 + 지급됩니다. +
+
+
+ • 본 약관은 동의 이후에도 {'"'}마이페이지{">"}새터반 택시대제전 이벤트 + 참여 약관{'"'}에서 다시 확인하실 수 있습니다.{" "} +
+ {isAgreeOnTermsOfEvent ? ( + <> +
+ +
+
+ 전화번호 + +
+
+ 새터반 + +
+ + + ) : ( + <> + {((isLogin && isEligible) || (isInvited && inviterInfo)) && ( + <> +
+ + + )} +
+ {isLogin && isEligible && ( + <> +
+ 전화번호 + +
+
+ 새터반 + { + const number = parseInt(value, 10); + setGroup(number); + }} + placeholder="숫자만 입력하세요" + css={{ width: "100%", marginLeft: "10px" }} + /> +
+ + )} + {isInvited && inviterInfo && ( +
+ 추천인 +
+ +
+ + {inviterInfo?.nickname} + +
+ )} + {isLogin ? ( + + ) : ( + + + + )} + + )} + + ); +}; + +export default ModalEvent2024SpringJoin; diff --git a/packages/web/src/components/ModalPopup/ModalEvent2024SpringShare.tsx b/packages/web/src/components/ModalPopup/ModalEvent2024SpringShare.tsx new file mode 100644 index 000000000..8c0f8f185 --- /dev/null +++ b/packages/web/src/components/ModalPopup/ModalEvent2024SpringShare.tsx @@ -0,0 +1,49 @@ +import Modal from "@/components/Modal"; + +import BodyEvent2024SpringShare, { + BodyEvent2024SpringShareProps, +} from "./Body/BodyEvent2024SpringShare"; + +import theme from "@/tools/theme"; + +import ShareRoundedIcon from "@mui/icons-material/ShareRounded"; + +type ModalEvent2024SpringShareProps = { + isOpen: boolean; + onChangeIsOpen?: (isOpen: boolean) => void; + inviteUrl: BodyEvent2024SpringShareProps["inviteUrl"]; +}; + +const ModalEvent2024SpringShare = ({ + isOpen, + onChangeIsOpen, + inviteUrl, +}: ModalEvent2024SpringShareProps) => { + const styleTitle = { + ...theme.font18, + display: "flex", + alignItems: "center", + margin: "0 8px 12px", + }; + const styleIcon = { + fontSize: "21px", + margin: "0 4px 0 0", + }; + + return ( + +
+ + 이벤트 공유하기 +
+ +
+ ); +}; + +export default ModalEvent2024SpringShare; +export { BodyEvent2024SpringShare }; diff --git a/src/components/ModalPopup/ModalMypageModify.tsx b/packages/web/src/components/ModalPopup/ModalMypageModify.tsx similarity index 80% rename from src/components/ModalPopup/ModalMypageModify.tsx rename to packages/web/src/components/ModalPopup/ModalMypageModify.tsx index 6ad12e8ed..dcd267021 100644 --- a/src/components/ModalPopup/ModalMypageModify.tsx +++ b/packages/web/src/components/ModalPopup/ModalMypageModify.tsx @@ -2,37 +2,34 @@ import axiosOri from "axios"; import { useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; +import { useEvent2024SpringQuestComplete } from "@/hooks/event/useEvent2024SpringQuestComplete"; import { useFetchRecoilState, useValueRecoilState, -} from "hooks/useFetchRecoilState"; -import { useAxios } from "hooks/useTaxiAPI"; +} from "@/hooks/useFetchRecoilState"; +import { useAxios } from "@/hooks/useTaxiAPI"; -import Button from "components/Button"; -import DottedLine from "components/DottedLine"; -import Input from "components/Input"; -import InputAccount from "components/Input/InputAccount"; -import Modal from "components/Modal"; -import ProfileImage from "components/User/ProfileImage"; +import Button from "@/components/Button"; +import DottedLine from "@/components/DottedLine"; +import Input from "@/components/Input"; +import InputAccount from "@/components/Input/InputAccount"; +import Modal from "@/components/Modal"; +import ProfileImage from "@/components/User/ProfileImage"; -import alertAtom from "atoms/alert"; +import alertAtom from "@/atoms/alert"; import { useSetRecoilState } from "recoil"; -import { convertImage } from "tools/image"; -import regExpTest from "tools/regExpTest"; -import theme from "tools/theme"; +import { convertImage } from "@/tools/image"; +import regExpTest from "@/tools/regExpTest"; +import theme from "@/tools/theme"; type ModalMypageModifyProps = Omit< Parameters[0], "padding" | "children" | "onEnter" -> & { profToken?: string; onUpdate?: () => void }; +>; type ProfileImageLargeProps = Parameters[0]; -type ButtonProfileImageProps = { - onUpdate?: ModalMypageModifyProps["onUpdate"]; -}; - const ProfileImageLarge = (props: ProfileImageLargeProps) => (
(
); -const ButtonProfileImage = ({ onUpdate }: ButtonProfileImageProps) => { +const ButtonProfileImage = () => { const { t } = useTranslation("mypage"); const axios = useAxios(); @@ -87,7 +84,6 @@ const ButtonProfileImage = ({ onUpdate }: ButtonProfileImageProps) => { }); if (data2?.result) { fetchLoginInfo(); - onUpdate?.(); setProfileAlert("SUCCESS"); return; } @@ -97,7 +93,7 @@ const ButtonProfileImage = ({ onUpdate }: ButtonProfileImageProps) => { } catch (e) { setProfileAlert("FAIL"); } - }, [onUpdate]); + }, []); const onClick = useCallback(() => { if (!profileAlert) inputRef.current?.click(); @@ -139,11 +135,7 @@ const ButtonProfileImage = ({ onUpdate }: ButtonProfileImageProps) => { ); }; -const ModalMypageModify = ({ - profToken, - onUpdate, - ...modalProps -}: ModalMypageModifyProps) => { +const ModalMypageModify = ({ ...modalProps }: ModalMypageModifyProps) => { const { t } = useTranslation("mypage"); const axios = useAxios(); @@ -152,6 +144,9 @@ const ModalMypageModify = ({ const loginInfo = useValueRecoilState("loginInfo"); const fetchLoginInfo = useFetchRecoilState("loginInfo"); + //#region event2024Spring + const event2024SpringQuestComplete = useEvent2024SpringQuestComplete(); + //#endregion const setAlert = useSetRecoilState(alertAtom); useEffect(() => { @@ -174,6 +169,9 @@ const ModalMypageModify = ({ method: "post", data: { nickname }, onError: () => setAlert(t("page_modify.nickname_failed")), + //#region event2024Spring + onSuccess: () => event2024SpringQuestComplete("nicknameChanging"), // event2024Spring + //#endregion }); } if (account !== loginInfo?.account) { @@ -183,6 +181,9 @@ const ModalMypageModify = ({ method: "post", data: { account }, onError: () => setAlert(t("page_modify.account_failed")), + //#region event2024Spring + onSuccess: () => event2024SpringQuestComplete("accountChanging"), // event2024Spring + //#endregion }); } if (isNeedToUpdateLoginInfo) { @@ -217,9 +218,9 @@ const ModalMypageModify = ({
{loginInfo?.name}
{loginInfo?.profileImgUrl && ( - + )} - +
@@ -250,10 +251,12 @@ const ModalMypageModify = ({
-
앱에서 보기 -
+
diff --git a/src/components/Skeleton/index.tsx b/packages/web/src/components/Skeleton/index.tsx similarity index 55% rename from src/components/Skeleton/index.tsx rename to packages/web/src/components/Skeleton/index.tsx index ed41e8402..46a2bb9db 100644 --- a/src/components/Skeleton/index.tsx +++ b/packages/web/src/components/Skeleton/index.tsx @@ -1,32 +1,32 @@ import { ReactNode, useMemo } from "react"; import { useLocation } from "react-router-dom"; -import useCSSVariablesEffect from "hooks/skeleton/useCSSVariablesEffect"; -import useChannelTalkEffect from "hooks/skeleton/useChannelTalkEffect"; -import useFirebaseMessagingEffect from "hooks/skeleton/useFirebaseMessagingEffect"; -import useFlutterEventCommunicationEffect from "hooks/skeleton/useFlutterEventCommunicationEffect"; -import useGoogleAnalyticsEffect from "hooks/skeleton/useGoogleAnalyticsEffect"; -import useI18nextEffect from "hooks/skeleton/useI18nextEffect"; -import useScrollRestorationEffect from "hooks/skeleton/useScrollRestorationEffect"; -import useVirtualKeyboardDetectEffect from "hooks/skeleton/useVirtualKeyboardDetectEffect"; +import { useEvent2024SpringEffect } from "@/hooks/event/useEvent2024SpringEffect"; +import useCSSVariablesEffect from "@/hooks/skeleton/useCSSVariablesEffect"; +import useChannelTalkEffect from "@/hooks/skeleton/useChannelTalkEffect"; +import useFirebaseMessagingEffect from "@/hooks/skeleton/useFirebaseMessagingEffect"; +import useFlutterEventCommunicationEffect from "@/hooks/skeleton/useFlutterEventCommunicationEffect"; +import useGoogleAnalyticsEffect from "@/hooks/skeleton/useGoogleAnalyticsEffect"; +import useI18nextEffect from "@/hooks/skeleton/useI18nextEffect"; +import useScrollRestorationEffect from "@/hooks/skeleton/useScrollRestorationEffect"; +import useVirtualKeyboardDetectEffect from "@/hooks/skeleton/useVirtualKeyboardDetectEffect"; import { useSyncRecoilStateEffect, useValueRecoilState, -} from "hooks/useFetchRecoilState"; +} from "@/hooks/useFetchRecoilState"; -import HeaderBar from "components/HeaderBar"; -import Loading from "components/Loading"; -import { ModalTerms } from "components/ModalPopup"; -import Error from "pages/Error"; +import HeaderBar from "@/components/Header/HeaderBar"; +import Loading from "@/components/Loading"; +import { ModalTerms } from "@/components/ModalPopup"; +import Error from "@/pages/Error"; import Navigation from "./Navigation"; import SuggestAppTopBar from "./SuggestAppTopBar"; -import errorAtom from "atoms/error"; -import isAppAtom from "atoms/isApp"; +import errorAtom from "@/atoms/error"; import { useRecoilValue } from "recoil"; -import isMobile from "tools/isMobile"; +import { deviceType } from "@/tools/loadenv"; type ContainerProps = { children: ReactNode; @@ -51,11 +51,8 @@ const Container = ({ children }: ContainerProps) => ( ); const Skeleton = ({ children }: SkeletonProps) => { - const { - id: userId, - agreeOnTermsOfService: isAgreeOnTermsOfService, - deviceType, - } = useValueRecoilState("loginInfo") || {}; + const { id: userId, agreeOnTermsOfService: isAgreeOnTermsOfService } = + useValueRecoilState("loginInfo") || {}; const { pathname } = useLocation(); const error = useRecoilValue(errorAtom); const isLoading = userId === null; @@ -67,9 +64,9 @@ const Skeleton = ({ children }: SkeletonProps) => { [pathname] ); - const isApp = useRecoilValue(isAppAtom) || deviceType === "app"; - const [isAndroid, isIOS] = isMobile(); - + //#region event2023Fall + useEvent2024SpringEffect(); + //#endregion useSyncRecoilStateEffect(); // loginIngo, taxiLocations, myRooms, notificationOptions 초기화 및 동기화 useI18nextEffect(); useScrollRestorationEffect(); @@ -90,12 +87,18 @@ const Skeleton = ({ children }: SkeletonProps) => { ) : ( <> {isDisplayNavigation && } - {isDisplayNavigation && (isAndroid || isIOS) && !isApp && ( + {isDisplayNavigation && deviceType.startsWith("mobile/") && ( )} {children} - {isDisplayNavigation &&
} + {isDisplayNavigation && ( +
+ )} )} diff --git a/src/components/Title.tsx b/packages/web/src/components/Title.tsx similarity index 78% rename from src/components/Title.tsx rename to packages/web/src/components/Title.tsx index 20472a384..16cc933b6 100644 --- a/src/components/Title.tsx +++ b/packages/web/src/components/Title.tsx @@ -1,11 +1,14 @@ import { ReactNode, memo } from "react"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; import AddRoundedIcon from "@mui/icons-material/AddRounded"; +import ConfirmationNumberRoundedIcon from "@mui/icons-material/ConfirmationNumberRounded"; +import EmojiEventsRoundedIcon from "@mui/icons-material/EmojiEventsRounded"; import ErrorOutlineRoundedIcon from "@mui/icons-material/ErrorOutlineRounded"; import FavoriteRoundedIocn from "@mui/icons-material/FavoriteRounded"; import FeedRounedIcon from "@mui/icons-material/FeedRounded"; +import FestivalRoundedIcon from "@mui/icons-material/FestivalRounded"; import HailRoundedIcon from "@mui/icons-material/HailRounded"; import HistoryRoundedIcon from "@mui/icons-material/HistoryRounded"; import LocalTaxiRoundedIcon from "@mui/icons-material/LocalTaxiRounded"; @@ -14,6 +17,7 @@ import NotificationsActiveRoundedIcon from "@mui/icons-material/NotificationsAct import PersonOutlineRoundedIcon from "@mui/icons-material/PersonOutlineRounded"; import QuestionAnswerRoundedIcon from "@mui/icons-material/QuestionAnswerRounded"; import SearchRoundedIcon from "@mui/icons-material/SearchRounded"; +import ShoppingCartRoundedIcon from "@mui/icons-material/ShoppingCartRounded"; import SubjectRoundedIcon from "@mui/icons-material/SubjectRounded"; type IconProps = { @@ -30,7 +34,11 @@ type IconProps = { | "error" | "feed" | "favorite" - | "notice"; + | "notice" + | "ticket" + | "festival" + | "shop" + | "leaderboard"; }; type TitleProps = { @@ -73,6 +81,14 @@ const Icon = ({ type: icon }: IconProps) => { return ; case "notice": return ; + case "ticket": + return ; + case "festival": + return ; + case "shop": + return ; + case "leaderboard": + return ; default: return <>; } @@ -91,7 +107,7 @@ const Title = ({ icon, children, isHeader = false }: TitleProps) => ( css={{ ...theme.font20, color: theme.purple, - marginLeft: "8px", + marginLeft: icon ? "8px" : undefined, }} > {children} diff --git a/src/components/Toggle.tsx b/packages/web/src/components/Toggle.tsx similarity index 93% rename from src/components/Toggle.tsx rename to packages/web/src/components/Toggle.tsx index 9e6afb199..cdf2ba2fc 100644 --- a/src/components/Toggle.tsx +++ b/packages/web/src/components/Toggle.tsx @@ -1,8 +1,8 @@ import { useCallback } from "react"; -import useHoverProps from "hooks/theme/useHoverProps"; +import useHoverProps from "@/hooks/theme/useHoverProps"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; type ToggleProps = { value: boolean; diff --git a/src/components/Tooltip.tsx b/packages/web/src/components/Tooltip.tsx similarity index 97% rename from src/components/Tooltip.tsx rename to packages/web/src/components/Tooltip.tsx index 433007113..7cfa805c8 100644 --- a/src/components/Tooltip.tsx +++ b/packages/web/src/components/Tooltip.tsx @@ -1,4 +1,4 @@ -import theme from "tools/theme"; +import theme from "@/tools/theme"; import Tooltip from "@mui/material/Tooltip"; diff --git a/src/components/User/ProfileImage.tsx b/packages/web/src/components/User/ProfileImage.tsx similarity index 61% rename from src/components/User/ProfileImage.tsx rename to packages/web/src/components/User/ProfileImage.tsx index 58bb297cd..657f4e2e8 100644 --- a/src/components/User/ProfileImage.tsx +++ b/packages/web/src/components/User/ProfileImage.tsx @@ -1,20 +1,19 @@ import { useEffect, useState } from "react"; -import theme from "tools/theme"; -import { getS3Url } from "tools/trans"; +import theme from "@/tools/theme"; -import defaultImg from "static/assets/profileImgOnError.png"; +import defaultImg from "@/static/assets/profileImgOnError.png"; type ProfileImageProps = { url: string; - token?: string; }; -const ProfileImage = ({ url, token }: ProfileImageProps) => { - const getSrc = () => getS3Url(`/profile-img/${url}?token=${token || ""}`); - const [src, setSrc] = useState(getSrc()); +const ProfileImage = ({ url }: ProfileImageProps) => { + const [src, setSrc] = useState(url); - useEffect(() => setSrc(getSrc()), [url, token]); + useEffect(() => { + setSrc(url); + }, [url]); return (
( @@ -18,9 +18,11 @@ const WhiteContainerSuggestLogin = () => ( diff --git a/packages/web/src/components/WhiteContainer/index.tsx b/packages/web/src/components/WhiteContainer/index.tsx new file mode 100644 index 000000000..3851828e0 --- /dev/null +++ b/packages/web/src/components/WhiteContainer/index.tsx @@ -0,0 +1,26 @@ +import { HTMLProps, ReactNode } from "react"; + +import theme from "@/tools/theme"; + +type WhiteContainerProps = { + children?: ReactNode; +} & HTMLProps; + +const WhiteContainer = ({ children, ...htmlProps }: WhiteContainerProps) => ( +
+ {children} +
+); + +export default WhiteContainer; diff --git a/src/hooks/chat/useAccountFromChats.tsx b/packages/web/src/hooks/chat/useAccountFromChats.tsx similarity index 89% rename from src/hooks/chat/useAccountFromChats.tsx rename to packages/web/src/hooks/chat/useAccountFromChats.tsx index a71fa79b3..8e82f287a 100644 --- a/src/hooks/chat/useAccountFromChats.tsx +++ b/packages/web/src/hooks/chat/useAccountFromChats.tsx @@ -1,6 +1,6 @@ import { useMemo } from "react"; -import type { Chats } from "types/chat"; +import type { Chats } from "@/types/chat"; export default (chats: Chats): Nullable => { return useMemo(() => { diff --git a/src/hooks/chat/useBodyScrollControllerEffect.tsx b/packages/web/src/hooks/chat/useBodyScrollControllerEffect.tsx similarity index 95% rename from src/hooks/chat/useBodyScrollControllerEffect.tsx rename to packages/web/src/hooks/chat/useBodyScrollControllerEffect.tsx index 4d3149b93..86c5a078e 100644 --- a/src/hooks/chat/useBodyScrollControllerEffect.tsx +++ b/packages/web/src/hooks/chat/useBodyScrollControllerEffect.tsx @@ -1,15 +1,15 @@ import { RefObject, useEffect, useLayoutEffect, useRef } from "react"; -import type { Chat, Chats } from "types/chat"; +import type { Chat, Chats } from "@/types/chat"; -import { useAxios } from "hooks/useTaxiAPI"; +import { useAxios } from "@/hooks/useTaxiAPI"; import { isBottomOnScroll, isTopOnScroll, scrollToBottom, -} from "tools/chat/scroll"; -import { socketReady } from "tools/socket"; +} from "@/tools/chat/scroll"; +import { socketReady } from "@/tools/socket"; const mutationObserverConfig = { childList: true, @@ -123,6 +123,7 @@ export default ( const cleandChat = chats.filter( (chat) => !("isSpecialChat" in chat) ) as Array; + if (!cleandChat?.[0]?.time) return; axios({ url: "/chats/load/before", method: "post", diff --git a/src/hooks/chat/useChatsForBody.tsx b/packages/web/src/hooks/chat/useChatsForBody.tsx similarity index 74% rename from src/hooks/chat/useChatsForBody.tsx rename to packages/web/src/hooks/chat/useChatsForBody.tsx index 8bfc126d3..6f9dbfc50 100644 --- a/src/hooks/chat/useChatsForBody.tsx +++ b/packages/web/src/hooks/chat/useChatsForBody.tsx @@ -1,18 +1,18 @@ import { ReactNode, useMemo } from "react"; -import type { BotChat, Chats, LayoutType, UserChat } from "types/chat"; +import type { BotChat, Chats, LayoutType, UserChat } from "@/types/chat"; -import { useValueRecoilState } from "hooks/useFetchRecoilState"; +import { useValueRecoilState } from "@/hooks/useFetchRecoilState"; -import MessageDate from "components/Chat/MessagesBody/MessageDate"; -import MessageInOut from "components/Chat/MessagesBody/MessageInOut"; -import MessageInfScroll from "components/Chat/MessagesBody/MessageInfScroll"; -import MessageJoint from "components/Chat/MessagesBody/MessageJoint"; -import MessageSet from "components/Chat/MessagesBody/MessageSet"; +import MessageDate from "@/components/Chat/MessagesBody/MessageDate"; +import MessageInOut from "@/components/Chat/MessagesBody/MessageInOut"; +import MessageInfScroll from "@/components/Chat/MessagesBody/MessageInfScroll"; +import MessageJoint from "@/components/Chat/MessagesBody/MessageJoint"; +import MessageSet from "@/components/Chat/MessagesBody/MessageSet"; -import { getChatUniquewKey } from "tools/chat/chats"; -import dayjs from "tools/day"; -import moment from "tools/moment"; +import { getChatUniquewKey } from "@/tools/chat/chats"; +import dayjs from "@/tools/day"; +import moment from "@/tools/moment"; export default (_chats: Chats, layoutType: LayoutType, roomInfo: Room) => { const { oid: userOid } = useValueRecoilState("loginInfo") || {}; @@ -81,8 +81,14 @@ export default (_chats: Chats, layoutType: LayoutType, roomInfo: Room) => { item.type === "payment" || item.type === "settlement" || item.type === "account" || - item.type === "share" + item.type === "share" || + item.type === "departure" || + item.type === "arrival" ) { + if (["share", "departure", "arrival"].includes(item.type)) { + item.authorId = "bot"; + item.authorName = "택시 봇"; + } if ( chatsCache && (chatsCache[0].authorId !== item.authorId || diff --git a/src/hooks/chat/useIsReadyToLoadImage.tsx b/packages/web/src/hooks/chat/useIsReadyToLoadImage.tsx similarity index 100% rename from src/hooks/chat/useIsReadyToLoadImage.tsx rename to packages/web/src/hooks/chat/useIsReadyToLoadImage.tsx diff --git a/src/hooks/chat/useSendMessage.tsx b/packages/web/src/hooks/chat/useSendMessage.tsx similarity index 94% rename from src/hooks/chat/useSendMessage.tsx rename to packages/web/src/hooks/chat/useSendMessage.tsx index 2fb942be7..401633095 100644 --- a/src/hooks/chat/useSendMessage.tsx +++ b/packages/web/src/hooks/chat/useSendMessage.tsx @@ -1,12 +1,12 @@ import axiosOri from "axios"; import { MutableRefObject, useCallback } from "react"; -import { useAxios } from "hooks/useTaxiAPI"; +import { useAxios } from "@/hooks/useTaxiAPI"; -import alertAtom from "atoms/alert"; +import alertAtom from "@/atoms/alert"; import { useSetRecoilState } from "recoil"; -import regExpTest from "tools/regExpTest"; +import regExpTest from "@/tools/regExpTest"; export default ( roomId: string, diff --git a/src/hooks/chat/useSocketChatEffect.tsx b/packages/web/src/hooks/chat/useSocketChatEffect.tsx similarity index 93% rename from src/hooks/chat/useSocketChatEffect.tsx rename to packages/web/src/hooks/chat/useSocketChatEffect.tsx index 0da29cf0e..cb0828fb2 100644 --- a/src/hooks/chat/useSocketChatEffect.tsx +++ b/packages/web/src/hooks/chat/useSocketChatEffect.tsx @@ -1,23 +1,23 @@ import { MutableRefObject, RefObject, useEffect } from "react"; import { useStateWithCallbackLazy } from "use-state-with-callback"; -import type { Chat, Chats } from "types/chat"; +import type { Chat, Chats } from "@/types/chat"; -import { useValueRecoilState } from "hooks/useFetchRecoilState"; -import { useAxios } from "hooks/useTaxiAPI"; +import { useValueRecoilState } from "@/hooks/useFetchRecoilState"; +import { useAxios } from "@/hooks/useTaxiAPI"; import { createInfScrollCheckoutChat, createShareChat, getCleanupChats, jointCheckoutChat, -} from "tools/chat/chats"; -import { isBottomOnScroll, scrollToBottom } from "tools/chat/scroll"; +} from "@/tools/chat/chats"; +import { isBottomOnScroll, scrollToBottom } from "@/tools/chat/scroll"; import { registerSocketEventListener, resetSocketEventListener, socketReady, -} from "tools/socket"; +} from "@/tools/socket"; export default ( roomInfo: Nullable, diff --git a/packages/web/src/hooks/event/useEvent2023FallEffect.ts b/packages/web/src/hooks/event/useEvent2023FallEffect.ts new file mode 100644 index 000000000..abae6e49a --- /dev/null +++ b/packages/web/src/hooks/event/useEvent2023FallEffect.ts @@ -0,0 +1,45 @@ +import { useEffect, useRef } from "react"; + +import type { QuestId } from "@/types/event2023fall"; + +import { useValueRecoilState } from "@/hooks/useFetchRecoilState"; + +import toast from "@/tools/toast"; + +export const useEvent2023FallEffect = () => { + const { completedQuests, quests } = + useValueRecoilState("event2023FallInfo") || {}; + + const prevEventStatusRef = useRef(); + + useEffect(() => { + if (!completedQuests || !quests) return; + prevEventStatusRef.current = prevEventStatusRef.current || completedQuests; + if (prevEventStatusRef.current.length === completedQuests.length) return; + + const questsForCompare = [...completedQuests]; + prevEventStatusRef.current.forEach((questId) => { + const index = questsForCompare.indexOf(questId); + if (index < 0) return; + questsForCompare.splice(index, 1); + }); + questsForCompare.forEach((questId) => { + const quest = quests.find(({ id }) => id === questId); + if (!quest) return; + const notificationValue = { + type: "default" as const, + imageUrl: quest.imageUrl, + title: "퀘스트 완료", + subtitle: "한가위 송편 이벤트", + content: `축하합니다! "${quest.name}" 퀘스트를 달성하여 ${ + quest.reward.ticket1 + ? `일반 응모권 ${quest.reward.ticket1}개를` + : `송편 ${quest.reward.credit}개를` + } 획득하셨습니다.`, + button: { text: "확인하기", path: "/event/2023fall-missions" }, + }; + toast(notificationValue); + }); + prevEventStatusRef.current = completedQuests; + }, [completedQuests]); +}; diff --git a/packages/web/src/hooks/event/useEvent2023FallQuestComplete.ts b/packages/web/src/hooks/event/useEvent2023FallQuestComplete.ts new file mode 100644 index 000000000..b01cbe153 --- /dev/null +++ b/packages/web/src/hooks/event/useEvent2023FallQuestComplete.ts @@ -0,0 +1,42 @@ +import { useCallback } from "react"; + +import type { QuestId } from "@/types/event2023fall"; + +import { + useFetchRecoilState, + useValueRecoilState, +} from "@/hooks/useFetchRecoilState"; +import { useAxios } from "@/hooks/useTaxiAPI"; + +export const useEvent2023FallQuestComplete = () => { + const axios = useAxios(); + const fetchEvent2023FallInfo = useFetchRecoilState("event2023FallInfo"); + const { completedQuests, quests } = + useValueRecoilState("event2023FallInfo") || {}; + + return useCallback( + (id: QuestId) => { + if (!completedQuests || !quests) return; + const questMaxCount = + quests?.find((quest) => quest.id === id)?.maxCount || 0; + const questCompletedCount = completedQuests?.filter( + (questId) => questId === id + ).length; + if (questCompletedCount >= questMaxCount) return; + if ( + [ + "roomSharing", + "eventSharingOnInstagram", + "purchaseSharingOnInstagram", + ].includes(id) + ) { + axios({ + url: `/events/2023fall/quests/complete/${id}`, + method: "post", + onSuccess: () => fetchEvent2023FallInfo(), + }); + } else fetchEvent2023FallInfo(); + }, + [completedQuests, fetchEvent2023FallInfo, quests] + ); +}; diff --git a/packages/web/src/hooks/event/useEvent2024SpringEffect.ts b/packages/web/src/hooks/event/useEvent2024SpringEffect.ts new file mode 100644 index 000000000..45f88298e --- /dev/null +++ b/packages/web/src/hooks/event/useEvent2024SpringEffect.ts @@ -0,0 +1,41 @@ +import { useEffect, useRef } from "react"; + +import type { QuestId } from "@/types/event2024spring"; + +import { useValueRecoilState } from "@/hooks/useFetchRecoilState"; + +import toast from "@/tools/toast"; + +export const useEvent2024SpringEffect = () => { + const { completedQuests, quests } = + useValueRecoilState("event2024SpringInfo") || {}; + + const prevEventStatusRef = useRef(); + + useEffect(() => { + if (!completedQuests || !quests) return; + prevEventStatusRef.current = prevEventStatusRef.current || completedQuests; + if (prevEventStatusRef.current.length === completedQuests.length) return; + + const questsForCompare = [...completedQuests]; + prevEventStatusRef.current.forEach((questId) => { + const index = questsForCompare.indexOf(questId); + if (index < 0) return; + questsForCompare.splice(index, 1); + }); + questsForCompare.forEach((questId) => { + const quest = quests.find(({ id }) => id === questId); + if (!quest) return; + const notificationValue = { + type: "default" as const, + imageUrl: quest.imageUrl, + title: "퀘스트 완료", + subtitle: "새터반 택시대제전", + content: `축하합니다! "${quest.name}" 퀘스트를 달성하여 넙죽코인 ${quest.reward.credit}개를 획득하셨습니다.`, + button: { text: "확인하기", path: "/event/2024spring-missions" }, + }; + toast(notificationValue); + }); + prevEventStatusRef.current = completedQuests; + }, [completedQuests]); +}; diff --git a/packages/web/src/hooks/event/useEvent2024SpringQuestComplete.ts b/packages/web/src/hooks/event/useEvent2024SpringQuestComplete.ts new file mode 100644 index 000000000..45adf4c3c --- /dev/null +++ b/packages/web/src/hooks/event/useEvent2024SpringQuestComplete.ts @@ -0,0 +1,36 @@ +import { useCallback } from "react"; + +import type { QuestId } from "@/types/event2024spring"; + +import { + useFetchRecoilState, + useValueRecoilState, +} from "@/hooks/useFetchRecoilState"; +import { useAxios } from "@/hooks/useTaxiAPI"; + +export const useEvent2024SpringQuestComplete = () => { + const axios = useAxios(); + const fetchEvent2024SpringInfo = useFetchRecoilState("event2024SpringInfo"); + const { completedQuests, quests } = + useValueRecoilState("event2024SpringInfo") || {}; + + return useCallback( + (id: QuestId) => { + if (!completedQuests || !quests) return; + const questMaxCount = + quests?.find((quest) => quest.id === id)?.maxCount || 0; + const questCompletedCount = completedQuests?.filter( + (questId) => questId === id + ).length; + if (questMaxCount > 0 && questCompletedCount >= questMaxCount) return; + if (["roomSharing"].includes(id)) { + axios({ + url: `/events/2024spring/quests/complete/${id}`, + method: "post", + onSuccess: () => fetchEvent2024SpringInfo(), + }); + } else fetchEvent2024SpringInfo(); + }, + [completedQuests, fetchEvent2024SpringInfo, quests] + ); +}; diff --git a/packages/web/src/hooks/event/useEventBackgroundEffect.ts b/packages/web/src/hooks/event/useEventBackgroundEffect.ts new file mode 100644 index 000000000..1543bd0de --- /dev/null +++ b/packages/web/src/hooks/event/useEventBackgroundEffect.ts @@ -0,0 +1,13 @@ +import { CSSProperties, useEffect } from "react"; + +export const useEventBackgroundEffect = ( + color: CSSProperties["color"] = "#000000" +) => { + useEffect(() => { + const prevBackground = document.body.style.background; + document.body.style.background = color; + return () => { + document.body.style.background = prevBackground; + }; + }, [color]); +}; diff --git a/src/hooks/skeleton/useCSSVariablesEffect.tsx b/packages/web/src/hooks/skeleton/useCSSVariablesEffect.tsx similarity index 100% rename from src/hooks/skeleton/useCSSVariablesEffect.tsx rename to packages/web/src/hooks/skeleton/useCSSVariablesEffect.tsx diff --git a/src/hooks/skeleton/useChannelTalkEffect/channelService.js b/packages/web/src/hooks/skeleton/useChannelTalkEffect/channelService.js similarity index 100% rename from src/hooks/skeleton/useChannelTalkEffect/channelService.js rename to packages/web/src/hooks/skeleton/useChannelTalkEffect/channelService.js diff --git a/src/hooks/skeleton/useChannelTalkEffect/index.tsx b/packages/web/src/hooks/skeleton/useChannelTalkEffect/index.tsx similarity index 87% rename from src/hooks/skeleton/useChannelTalkEffect/index.tsx rename to packages/web/src/hooks/skeleton/useChannelTalkEffect/index.tsx index fffe335ca..3f2dec5b8 100644 --- a/src/hooks/skeleton/useChannelTalkEffect/index.tsx +++ b/packages/web/src/hooks/skeleton/useChannelTalkEffect/index.tsx @@ -3,11 +3,11 @@ import { useLocation } from "react-router-dom"; import ChannelService from "./channelService"; -import errorAtom from "atoms/error"; -import loginInfoAtom from "atoms/loginInfo"; +import errorAtom from "@/atoms/error"; +import loginInfoAtom from "@/atoms/loginInfo"; import { useRecoilValue } from "recoil"; -import { channelTalkPluginKey } from "loadenv"; +import { channelTalkPluginKey } from "@/tools/loadenv"; export default () => { const location = useLocation(); diff --git a/src/hooks/skeleton/useFirebaseMessagingEffect.tsx b/packages/web/src/hooks/skeleton/useFirebaseMessagingEffect.tsx similarity index 95% rename from src/hooks/skeleton/useFirebaseMessagingEffect.tsx rename to packages/web/src/hooks/skeleton/useFirebaseMessagingEffect.tsx index 5b028f15c..a35770ded 100644 --- a/src/hooks/skeleton/useFirebaseMessagingEffect.tsx +++ b/packages/web/src/hooks/skeleton/useFirebaseMessagingEffect.tsx @@ -5,10 +5,10 @@ import { useCallback, useEffect } from "react"; import { useFetchRecoilState, useValueRecoilState, -} from "hooks/useFetchRecoilState"; -import { useAxios } from "hooks/useTaxiAPI"; +} from "@/hooks/useFetchRecoilState"; +import { useAxios } from "@/hooks/useTaxiAPI"; -import { firebaseConfig } from "loadenv"; +import { firebaseConfig } from "@/tools/loadenv"; const firebaseApp = firebaseConfig && initializeApp(firebaseConfig); diff --git a/packages/web/src/hooks/skeleton/useFlutterEventCommunicationEffect.tsx b/packages/web/src/hooks/skeleton/useFlutterEventCommunicationEffect.tsx new file mode 100644 index 000000000..b6cf4e38d --- /dev/null +++ b/packages/web/src/hooks/skeleton/useFlutterEventCommunicationEffect.tsx @@ -0,0 +1,218 @@ +import { useEffect, useState } from "react"; +import { useHistory } from "react-router-dom"; + +import { InAppNotification } from "@/types/inAppNotification"; + +import { + useFetchRecoilState, + useValueRecoilState, +} from "@/hooks/useFetchRecoilState"; +import { useAxios } from "@/hooks/useTaxiAPI"; + +import alertAtom from "@/atoms/alert"; +import errorAtom from "@/atoms/error"; +import { LoginInfoType } from "@/atoms/loginInfo"; +import { useSetRecoilState } from "recoil"; + +import { isNotificationOn } from "@/tools/trans"; + +/** flag variable to check if the webview is in Flutter */ +let isWebViewInFlutter: boolean = false; + +export default () => { + const axios = useAxios(); + const history = useHistory(); + const setAlert = useSetRecoilState(alertAtom); + const setError = useSetRecoilState(errorAtom); + const loginInfo = useValueRecoilState("loginInfo"); + const { id: userId } = loginInfo || {}; + const notificationOptions = useValueRecoilState("notificationOptions"); + const fetchLoginInfo = useFetchRecoilState("loginInfo"); + + const [isWebViewInFlutterState, setIsWebViewInFlutterState] = + useState(false); + + useEffect(() => { + const eventListeners: Array<{ + name: string; + listner: (event: any) => void; + }> = []; + + // Flutter에서 webview가 준비되었을 때, 호출됩니다. + eventListeners.push({ + name: "flutterInAppWebViewPlatformReady", + listner: () => { + isWebViewInFlutter = true; + setIsWebViewInFlutterState(true); + }, + }); + + // Flutter에서 logininfo 업데이트를 요청할 때, 호출됩니다. + eventListeners.push({ + name: "updateLoginInfo", + listner: () => fetchLoginInfo(), + }); + + // Flutter에서 에러를 전달할 때, 호출됩니다. + eventListeners.push({ + name: "createError", + listner: ({ + detail: { title, message }, + }: { + detail: { title: string; message: string }; + }) => setError({ title, message, record: null }), + }); + + // Flutter에서 Alert 모달을 띄울 때, 호출됩니다. + eventListeners.push({ + name: "createAlert", + listner: ({ detail }: { detail: string }) => setAlert(detail), + }); + + // Flutter에서 history에 새로운 path를 push 할 때, 호출됩니다. + eventListeners.push({ + name: "pushHistory", + listner: ({ detail }: { detail: string }) => history.push(detail), + }); + + eventListeners.forEach(({ name, listner }) => + window.addEventListener(name, listner) + ); + return () => + eventListeners.forEach(({ name, listner }) => + window.removeEventListener(name, listner) + ); + }, []); + + // Flutter에 변동된 로그인 정보 전달 + useEffect(() => { + const isLoading = loginInfo === null; + if (!isLoading && isWebViewInFlutterState) + sendAuthUpdateEventToFlutter(loginInfo); + }, [userId, isWebViewInFlutterState]); + + // Flutter에 초기 알림 설정 전달 + useEffect(() => { + const tryNotification = async () => { + const isAllowedFInFlutter = await sendTryNotificationEventToFlutter(); + if (!isAllowedFInFlutter) { + await axios({ + url: "/notifications/editOptions", + method: "post", + data: { + options: { + beforeDepart: false, + chatting: false, + notice: false, + }, + }, + onError: () => {}, + }); + } + }; + if ( + userId && + isWebViewInFlutterState && + isNotificationOn(notificationOptions) + ) { + tryNotification(); + } + }, [userId, notificationOptions, isWebViewInFlutterState]); +}; + +/** 로그인 정보 변동 시 Flutter에 이벤트를 전달합니다 */ +export const sendAuthUpdateEventToFlutter = async ( + loginInfo: LoginInfoType +) => { + if (!isWebViewInFlutter) return; + try { + await window.flutter_inappwebview.callHandler("auth_update", loginInfo); + } catch (e) { + console.error(e); + } +}; + +/** + * 버튼 클릭으로 인한 로그아웃 시 Flutter에 이벤트 전달합니다 + * 이용약관 미동의로 인한 로그아웃 시에도 이벤트를 전달합니다 + */ +export const sendAuthLogoutEventToFlutter = async () => { + if (!isWebViewInFlutter) return; + try { + await window.flutter_inappwebview.callHandler("auth_logout"); + } catch (e) { + console.error(e); + } +}; + +/** 알림을 "on"으로 설정 시 Flutter에게 이벤트를 전달하고 앱의 알림 설정 여부를 반환받습니다. */ +export const sendTryNotificationEventToFlutter = async (): Promise => { + if (!isWebViewInFlutter) return true; + try { + return await window.flutter_inappwebview.callHandler("try_notification"); + } catch (e) { + console.error(e); + return false; + } +}; + +/** + * 클립보드로 텍스트 복사 시 Flutter에게 이벤트를 전달합니다. + * 복사 여부를 반환받습니다. + */ +export const sendClipboardCopyEventToFlutter = async ( + value: string +): Promise => { + if (!isWebViewInFlutter) return false; + try { + const result = await window.flutter_inappwebview.callHandler( + "clipboard_copy", + value + ); + return !!result; + } catch (e) { + console.error(e); + return false; + } +}; + +/** + * 인앱 알림 발생 시 Flutter에 이벤트를 전달합니다. + * 인앱 알림 성공 여부를 반환받습니다. + */ +export const sendPopupInAppNotificationEventToFlutter = async ( + value: InAppNotification +): Promise => { + if (!isWebViewInFlutter) return false; + try { + const result = await window.flutter_inappwebview.callHandler( + "popup_inAppNotification", + value + ); + return !!result; + } catch (e) { + console.error(e); + return false; + } +}; + +/** + * 인스타그램 스토리 공유 이벤트 발생 시 Flutter에 이벤트를 전달합니다. + * 인스타그램 실행 여부를 반환받습니다. + */ +export const sendPopupInstagramStoryShareToFlutter = async (value: { + backgroundLayerUrl: string; + stickerLayerUrl: string; +}): Promise => { + if (!isWebViewInFlutter) return false; + try { + const result = await window.flutter_inappwebview.callHandler( + "popup_instagram_story_share", + value + ); + return !!result; + } catch (e) { + console.error(e); + return false; + } +}; diff --git a/src/hooks/skeleton/useGoogleAnalyticsEffect.tsx b/packages/web/src/hooks/skeleton/useGoogleAnalyticsEffect.tsx similarity index 85% rename from src/hooks/skeleton/useGoogleAnalyticsEffect.tsx rename to packages/web/src/hooks/skeleton/useGoogleAnalyticsEffect.tsx index 2b336da3f..9b5958c92 100644 --- a/src/hooks/skeleton/useGoogleAnalyticsEffect.tsx +++ b/packages/web/src/hooks/skeleton/useGoogleAnalyticsEffect.tsx @@ -2,10 +2,10 @@ import { useEffect, useRef } from "react"; import reactGA from "react-ga4"; import { useLocation } from "react-router-dom"; -import loginInfoAtom from "atoms/loginInfo"; +import loginInfoAtom from "@/atoms/loginInfo"; import { useRecoilValue } from "recoil"; -import { gaTrackingId, nodeEnv } from "loadenv"; +import { gaTrackingId, isDev } from "@/tools/loadenv"; export default () => { const gaInitialized = useRef(false); @@ -22,7 +22,7 @@ export default () => { if (!gaInitialized.current) { gaInitialized.current = true; reactGA.initialize(gaTrackingId, { - testMode: nodeEnv === "development", + testMode: isDev, }); } reactGA.send({ hitType: "pageview", page: pathname }); diff --git a/src/hooks/skeleton/useI18nextEffect.tsx b/packages/web/src/hooks/skeleton/useI18nextEffect.tsx similarity index 63% rename from src/hooks/skeleton/useI18nextEffect.tsx rename to packages/web/src/hooks/skeleton/useI18nextEffect.tsx index 6e1a9d095..125ff525e 100644 --- a/src/hooks/skeleton/useI18nextEffect.tsx +++ b/packages/web/src/hooks/skeleton/useI18nextEffect.tsx @@ -3,18 +3,18 @@ import LanguageDetector from "i18next-browser-languagedetector"; import { useEffect } from "react"; import { initReactI18next, useTranslation } from "react-i18next"; -import nsAddroomEN from "pages/Addroom/langs/en.json"; -import nsAddroomKO from "pages/Addroom/langs/ko.json"; -import nsHomeEN from "pages/Home/langs/en.json"; -import nsHomeKO from "pages/Home/langs/ko.json"; -import nsMypageEN from "pages/Mypage/langs/en.json"; -import nsMypageKO from "pages/Mypage/langs/ko.json"; -import nsMyroomEN from "pages/Myroom/langs/en.json"; -import nsMyroomKO from "pages/Myroom/langs/ko.json"; -import nsSearchEN from "pages/Search/langs/en.json"; -import nsSearchKO from "pages/Search/langs/ko.json"; +import nsAddroomEN from "@/pages/Addroom/langs/en.json"; +import nsAddroomKO from "@/pages/Addroom/langs/ko.json"; +import nsHomeEN from "@/pages/Home/langs/en.json"; +import nsHomeKO from "@/pages/Home/langs/ko.json"; +import nsMypageEN from "@/pages/Mypage/langs/en.json"; +import nsMypageKO from "@/pages/Mypage/langs/ko.json"; +import nsMyroomEN from "@/pages/Myroom/langs/en.json"; +import nsMyroomKO from "@/pages/Myroom/langs/ko.json"; +import nsSearchEN from "@/pages/Search/langs/en.json"; +import nsSearchKO from "@/pages/Search/langs/ko.json"; -import { nodeEnv } from "loadenv"; +import { isDev } from "@/tools/loadenv"; /** * {@link https://www.i18next.com/overview/configuration-options} @@ -40,7 +40,7 @@ i18n mypage: nsMypageEN, }, }, - debug: nodeEnv === "development", + debug: isDev, keySeparator: ".", fallbackLng: "ko", defaultNS: "mypage", // default namespace diff --git a/src/hooks/skeleton/useScrollRestorationEffect/index.tsx b/packages/web/src/hooks/skeleton/useScrollRestorationEffect/index.tsx similarity index 100% rename from src/hooks/skeleton/useScrollRestorationEffect/index.tsx rename to packages/web/src/hooks/skeleton/useScrollRestorationEffect/index.tsx diff --git a/src/hooks/skeleton/useScrollRestorationEffect/utils.ts b/packages/web/src/hooks/skeleton/useScrollRestorationEffect/utils.ts similarity index 100% rename from src/hooks/skeleton/useScrollRestorationEffect/utils.ts rename to packages/web/src/hooks/skeleton/useScrollRestorationEffect/utils.ts diff --git a/src/hooks/skeleton/useVirtualKeyboardDetectEffect.tsx b/packages/web/src/hooks/skeleton/useVirtualKeyboardDetectEffect.tsx similarity index 94% rename from src/hooks/skeleton/useVirtualKeyboardDetectEffect.tsx rename to packages/web/src/hooks/skeleton/useVirtualKeyboardDetectEffect.tsx index cc0c4db98..631879bb1 100644 --- a/src/hooks/skeleton/useVirtualKeyboardDetectEffect.tsx +++ b/packages/web/src/hooks/skeleton/useVirtualKeyboardDetectEffect.tsx @@ -1,13 +1,12 @@ import { useEffect } from "react"; -import isVirtualKeyboardDetectedAtom from "atoms/isVirtualKeyboardDetected"; +import isVirtualKeyboardDetectedAtom from "@/atoms/isVirtualKeyboardDetected"; import { useSetRecoilState } from "recoil"; -import isMobile from "tools/isMobile"; +import { deviceType } from "@/tools/loadenv"; export default () => { const setIsVKDetected = useSetRecoilState(isVirtualKeyboardDetectedAtom); - const [isAndroid, isIOS] = isMobile(); useEffect(() => { /* @@ -17,7 +16,7 @@ export default () => { * 하지만, Android는 input의 focus 여부가 키보드 활성화 여부와 일치하지 않습니다. * Android에서는 window의 resize이벤트 핸들러를 등록하여 키보드 활성화 여부를 알 수 있습니다. */ - if (isAndroid) { + if (deviceType.endsWith("/android")) { const minKeyboardHeight = 300; const resizeEvent = () => { const visualViewportHeight = visualViewport?.height; @@ -32,7 +31,7 @@ export default () => { visualViewport?.addEventListener("resize", resizeEvent); return () => visualViewport?.removeEventListener("resize", resizeEvent); } - if (isIOS) { + if (deviceType.endsWith("/ios")) { let focusedElement: Nullable = null; const onFocus = (e: FocusEvent) => { focusedElement = e.target; diff --git a/src/hooks/theme/useHoverProps.tsx b/packages/web/src/hooks/theme/useHoverProps.tsx similarity index 100% rename from src/hooks/theme/useHoverProps.tsx rename to packages/web/src/hooks/theme/useHoverProps.tsx diff --git a/src/hooks/useButterflyState.tsx b/packages/web/src/hooks/useButterflyState.tsx similarity index 96% rename from src/hooks/useButterflyState.tsx rename to packages/web/src/hooks/useButterflyState.tsx index 133613e2a..1d065bd77 100644 --- a/src/hooks/useButterflyState.tsx +++ b/packages/web/src/hooks/useButterflyState.tsx @@ -1,6 +1,6 @@ import { useEffect, useRef, useState } from "react"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; type ButterflyState = "wide" | "narrow" | "fold"; diff --git a/src/hooks/useDateToken.tsx b/packages/web/src/hooks/useDateToken.tsx similarity index 100% rename from src/hooks/useDateToken.tsx rename to packages/web/src/hooks/useDateToken.tsx diff --git a/src/hooks/useDelay.tsx b/packages/web/src/hooks/useDelay.tsx similarity index 100% rename from src/hooks/useDelay.tsx rename to packages/web/src/hooks/useDelay.tsx diff --git a/src/hooks/useDisableScrollEffect/index.css b/packages/web/src/hooks/useDisableScrollEffect/index.css similarity index 100% rename from src/hooks/useDisableScrollEffect/index.css rename to packages/web/src/hooks/useDisableScrollEffect/index.css diff --git a/src/hooks/useDisableScrollEffect/index.tsx b/packages/web/src/hooks/useDisableScrollEffect/index.tsx similarity index 100% rename from src/hooks/useDisableScrollEffect/index.tsx rename to packages/web/src/hooks/useDisableScrollEffect/index.tsx diff --git a/src/hooks/useFetchRecoilState/index.tsx b/packages/web/src/hooks/useFetchRecoilState/index.tsx similarity index 60% rename from src/hooks/useFetchRecoilState/index.tsx rename to packages/web/src/hooks/useFetchRecoilState/index.tsx index bb98a5d58..a6701ce67 100644 --- a/src/hooks/useFetchRecoilState/index.tsx +++ b/packages/web/src/hooks/useFetchRecoilState/index.tsx @@ -1,7 +1,15 @@ import { useEffect } from "react"; -import { useAxios } from "hooks/useTaxiAPI"; - +import { + useFetchEvent2023FallInfo, + useSetEvent2023FallInfo, + useValueEvent2023FallInfo, +} from "./useFetchEvent2023FallInfo"; +import { + useFetchEvent2024SpringInfo, + useSetEvent2024SpringInfo, + useValueEvent2024SpringInfo, +} from "./useFetchEvent2024SpringInfo"; import { useFetchLoginInfo, useSetLoginInfo, @@ -23,30 +31,28 @@ import { useValueTaxiLocations, } from "./useFetchTaxiLocations"; -import isAppAtom from "atoms/isApp"; -import { LoginInfoType } from "atoms/loginInfo"; -import { MyRoomsType } from "atoms/myRooms"; -import { notificationOptionsType } from "atoms/notificationOptions"; -import { TaxiLocationsType } from "atoms/taxiLocations"; -import { useRecoilValue } from "recoil"; - -import { - sendAuthUpdateEventToFlutter, - sendTryNotificationEventToFlutter, -} from "tools/sendEventToFlutter"; -import { isNotificationOn } from "tools/trans"; +import { Event2023FallInfoType } from "@/atoms/event2023FallInfo"; +import { Event2024SpringInfoType } from "@/atoms/event2024SpringInfo"; +import { LoginInfoType } from "@/atoms/loginInfo"; +import { MyRoomsType } from "@/atoms/myRooms"; +import { notificationOptionsType } from "@/atoms/notificationOptions"; +import { TaxiLocationsType } from "@/atoms/taxiLocations"; export type AtomName = | "loginInfo" | "taxiLocations" | "myRooms" - | "notificationOptions"; + | "notificationOptions" + | "event2023FallInfo" + | "event2024SpringInfo"; type useValueRecoilStateType = { (atomName: "loginInfo"): LoginInfoType; (atomName: "taxiLocations"): TaxiLocationsType; (atomName: "myRooms"): MyRoomsType; (atomName: "notificationOptions"): notificationOptionsType; + (atomName: "event2023FallInfo"): Event2023FallInfoType; + (atomName: "event2024SpringInfo"): Event2024SpringInfoType; }; const _useValueRecoilState = (atomName: AtomName) => { switch (atomName) { @@ -58,6 +64,10 @@ const _useValueRecoilState = (atomName: AtomName) => { return useValueMyRooms(); case "notificationOptions": return useValueNotificationOptions(); + case "event2023FallInfo": + return useValueEvent2023FallInfo(); + case "event2024SpringInfo": + return useValueEvent2024SpringInfo(); } }; export const useValueRecoilState = @@ -73,6 +83,10 @@ export const useSetRecoilState = (atomName: AtomName) => { return useSetMyRooms(); case "notificationOptions": return useSetNotificationOptions(); + case "event2023FallInfo": + return useSetEvent2023FallInfo(); + case "event2024SpringInfo": + return useSetEvent2024SpringInfo(); } }; @@ -86,14 +100,15 @@ export const useFetchRecoilState = (atomName: AtomName) => { return useFetchMyRooms(); case "notificationOptions": return useFetchNotificationOptions(); + case "event2023FallInfo": + return useFetchEvent2023FallInfo(); + case "event2024SpringInfo": + return useFetchEvent2024SpringInfo(); } }; export const useSyncRecoilStateEffect = () => { - const axios = useAxios(); - const isApp = useRecoilValue(isAppAtom); const loginInfo = useValueRecoilState("loginInfo"); - const notificationOptions = useValueRecoilState("notificationOptions"); const { id: userId, deviceToken } = loginInfo || {}; // userId 초기화 및 동기화 @@ -112,32 +127,16 @@ export const useSyncRecoilStateEffect = () => { const fetchNotificationOptions = useFetchRecoilState("notificationOptions"); useEffect(fetchNotificationOptions, [deviceToken]); - // Flutter에 변동된 로그인 정보 전달 - useEffect(() => { - const isLoading = loginInfo === null; - if (!isLoading && isApp) sendAuthUpdateEventToFlutter(loginInfo); - }, [userId, isApp]); + // event2023FallInfo 초기화 및 동기화 + const fetchEvent2023FallInfo = useFetchRecoilState("event2023FallInfo"); + useEffect(fetchEvent2023FallInfo, [userId]); + + // event2024SpringInfo 초기화 및 동기화 + const fetchEvent2024SpringInfo = useFetchRecoilState("event2024SpringInfo"); + useEffect(fetchEvent2024SpringInfo, [userId]); +}; - // Flutter에 초기 알림 설정 전달 - useEffect(() => { - const tryNotification = async () => { - const isAllowedFInFlutter = await sendTryNotificationEventToFlutter(); - if (!isAllowedFInFlutter) { - await axios({ - url: "/notifications/editOptions", - method: "post", - data: { - options: { - beforeDepart: false, - chatting: false, - notice: false, - }, - }, - onError: () => {}, - }); - } - }; - if (userId && isApp && isNotificationOn(notificationOptions)) - tryNotification(); - }, [userId, isApp, notificationOptions]); +export const useIsLogin = (): boolean => { + const isLogin = !!useValueRecoilState("loginInfo")?.id; + return isLogin; }; diff --git a/packages/web/src/hooks/useFetchRecoilState/useFetchEvent2023FallInfo.tsx b/packages/web/src/hooks/useFetchRecoilState/useFetchEvent2023FallInfo.tsx new file mode 100644 index 000000000..5609a77e5 --- /dev/null +++ b/packages/web/src/hooks/useFetchRecoilState/useFetchEvent2023FallInfo.tsx @@ -0,0 +1,31 @@ +import { useCallback } from "react"; + +import { useAxios } from "@/hooks/useTaxiAPI"; +import { AxiosOption } from "@/hooks/useTaxiAPI/useAxios"; + +import event2023FallInfoAtom from "@/atoms/event2023FallInfo"; +import { useRecoilValue, useSetRecoilState } from "recoil"; + +import { eventMode } from "@/tools/loadenv"; + +export const useValueEvent2023FallInfo = () => + useRecoilValue(event2023FallInfoAtom); +export const useSetEvent2023FallInfo = () => + useSetRecoilState(event2023FallInfoAtom); +export const useFetchEvent2023FallInfo = () => { + const setEvent2023FallInfo = useSetEvent2023FallInfo(); + const axios = useAxios(); + + return useCallback((onError?: AxiosOption["onError"]) => { + if (eventMode === "2023fall") { + axios({ + url: "/events/2023fall/global-state/", + method: "get", + onSuccess: (data) => setEvent2023FallInfo(data), + onError: onError, + }); + } else { + setEvent2023FallInfo(null); + } + }, []); +}; diff --git a/packages/web/src/hooks/useFetchRecoilState/useFetchEvent2024SpringInfo.tsx b/packages/web/src/hooks/useFetchRecoilState/useFetchEvent2024SpringInfo.tsx new file mode 100644 index 000000000..90c6020bb --- /dev/null +++ b/packages/web/src/hooks/useFetchRecoilState/useFetchEvent2024SpringInfo.tsx @@ -0,0 +1,31 @@ +import { useCallback } from "react"; + +import { useAxios } from "@/hooks/useTaxiAPI"; +import { AxiosOption } from "@/hooks/useTaxiAPI/useAxios"; + +import event2024SpringInfoAtom from "@/atoms/event2024SpringInfo"; +import { useRecoilValue, useSetRecoilState } from "recoil"; + +import { eventMode } from "@/tools/loadenv"; + +export const useValueEvent2024SpringInfo = () => + useRecoilValue(event2024SpringInfoAtom); +export const useSetEvent2024SpringInfo = () => + useSetRecoilState(event2024SpringInfoAtom); +export const useFetchEvent2024SpringInfo = () => { + const setEvent2024SpringInfo = useSetEvent2024SpringInfo(); + const axios = useAxios(); + + return useCallback((onError?: AxiosOption["onError"]) => { + if (eventMode === "2024spring") { + axios({ + url: "/events/2024spring/globalState/", + method: "get", + onSuccess: (data) => setEvent2024SpringInfo(data), + onError: onError, + }); + } else { + setEvent2024SpringInfo(null); + } + }, []); +}; diff --git a/src/hooks/useFetchRecoilState/useFetchLoginInfo.tsx b/packages/web/src/hooks/useFetchRecoilState/useFetchLoginInfo.tsx similarity index 79% rename from src/hooks/useFetchRecoilState/useFetchLoginInfo.tsx rename to packages/web/src/hooks/useFetchRecoilState/useFetchLoginInfo.tsx index 82e97e725..0bf826b2c 100644 --- a/src/hooks/useFetchRecoilState/useFetchLoginInfo.tsx +++ b/packages/web/src/hooks/useFetchRecoilState/useFetchLoginInfo.tsx @@ -1,9 +1,9 @@ import { useCallback } from "react"; -import { useAxios } from "hooks/useTaxiAPI"; -import { AxiosOption } from "hooks/useTaxiAPI/useAxios"; +import { useAxios } from "@/hooks/useTaxiAPI"; +import { AxiosOption } from "@/hooks/useTaxiAPI/useAxios"; -import loginInfoAtom from "atoms/loginInfo"; +import loginInfoAtom from "@/atoms/loginInfo"; import { useRecoilValue, useSetRecoilState } from "recoil"; export const useValueLoginInfo = () => useRecoilValue(loginInfoAtom); diff --git a/src/hooks/useFetchRecoilState/useFetchMyRooms.tsx b/packages/web/src/hooks/useFetchRecoilState/useFetchMyRooms.tsx similarity index 85% rename from src/hooks/useFetchRecoilState/useFetchMyRooms.tsx rename to packages/web/src/hooks/useFetchRecoilState/useFetchMyRooms.tsx index b7fe3df18..a95ea4988 100644 --- a/src/hooks/useFetchRecoilState/useFetchMyRooms.tsx +++ b/packages/web/src/hooks/useFetchRecoilState/useFetchMyRooms.tsx @@ -1,11 +1,11 @@ import { useCallback } from "react"; -import { useAxios } from "hooks/useTaxiAPI"; -import { AxiosOption } from "hooks/useTaxiAPI/useAxios"; +import { useAxios } from "@/hooks/useTaxiAPI"; +import { AxiosOption } from "@/hooks/useTaxiAPI/useAxios"; import { useValueRecoilState } from "."; -import myRoomsAtom from "atoms/myRooms"; +import myRoomsAtom from "@/atoms/myRooms"; import { useRecoilValue, useSetRecoilState } from "recoil"; export const useValueMyRooms = () => useRecoilValue(myRoomsAtom); diff --git a/src/hooks/useFetchRecoilState/useFetchNotificationOptions.tsx b/packages/web/src/hooks/useFetchRecoilState/useFetchNotificationOptions.tsx similarity index 84% rename from src/hooks/useFetchRecoilState/useFetchNotificationOptions.tsx rename to packages/web/src/hooks/useFetchRecoilState/useFetchNotificationOptions.tsx index fa0d79770..5367119ed 100644 --- a/src/hooks/useFetchRecoilState/useFetchNotificationOptions.tsx +++ b/packages/web/src/hooks/useFetchRecoilState/useFetchNotificationOptions.tsx @@ -1,11 +1,11 @@ import { useCallback } from "react"; -import { useAxios } from "hooks/useTaxiAPI"; -import { AxiosOption } from "hooks/useTaxiAPI/useAxios"; +import { useAxios } from "@/hooks/useTaxiAPI"; +import { AxiosOption } from "@/hooks/useTaxiAPI/useAxios"; import { useValueRecoilState } from "."; -import notificationOptionsAtom from "atoms/notificationOptions"; +import notificationOptionsAtom from "@/atoms/notificationOptions"; import { useRecoilValue, useSetRecoilState } from "recoil"; export const useValueNotificationOptions = () => diff --git a/src/hooks/useFetchRecoilState/useFetchTaxiLocations.tsx b/packages/web/src/hooks/useFetchRecoilState/useFetchTaxiLocations.tsx similarity index 80% rename from src/hooks/useFetchRecoilState/useFetchTaxiLocations.tsx rename to packages/web/src/hooks/useFetchRecoilState/useFetchTaxiLocations.tsx index baacf0714..20fae24f2 100644 --- a/src/hooks/useFetchRecoilState/useFetchTaxiLocations.tsx +++ b/packages/web/src/hooks/useFetchRecoilState/useFetchTaxiLocations.tsx @@ -1,9 +1,9 @@ import { useCallback } from "react"; -import { useAxios } from "hooks/useTaxiAPI"; -import { AxiosOption } from "hooks/useTaxiAPI/useAxios"; +import { useAxios } from "@/hooks/useTaxiAPI"; +import { AxiosOption } from "@/hooks/useTaxiAPI/useAxios"; -import taxiLocationsAtom from "atoms/taxiLocations"; +import taxiLocationsAtom from "@/atoms/taxiLocations"; import { useRecoilValue, useSetRecoilState } from "recoil"; export const useValueTaxiLocations = () => useRecoilValue(taxiLocationsAtom); diff --git a/src/hooks/useIsTimeOver.tsx b/packages/web/src/hooks/useIsTimeOver.tsx similarity index 91% rename from src/hooks/useIsTimeOver.tsx rename to packages/web/src/hooks/useIsTimeOver.tsx index 1bb943089..34b2ebef9 100644 --- a/src/hooks/useIsTimeOver.tsx +++ b/packages/web/src/hooks/useIsTimeOver.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -import { dayNowClient } from "tools/day"; +import { dayNowClient } from "@/tools/day"; export default (time: Dayjs): boolean => { const [isTimeOver, setIsTimeOver] = useState(time <= dayNowClient()); diff --git a/src/hooks/useKeyboardOperationEffect.tsx b/packages/web/src/hooks/useKeyboardOperationEffect.tsx similarity index 100% rename from src/hooks/useKeyboardOperationEffect.tsx rename to packages/web/src/hooks/useKeyboardOperationEffect.tsx diff --git a/src/hooks/usePageFromSearchParams.tsx b/packages/web/src/hooks/usePageFromSearchParams.tsx similarity index 100% rename from src/hooks/usePageFromSearchParams.tsx rename to packages/web/src/hooks/usePageFromSearchParams.tsx diff --git a/src/hooks/useTaxiAPI/axios.ts b/packages/web/src/hooks/useTaxiAPI/axios.ts similarity index 67% rename from src/hooks/useTaxiAPI/axios.ts rename to packages/web/src/hooks/useTaxiAPI/axios.ts index cd258391b..6dfc72d08 100644 --- a/src/hooks/useTaxiAPI/axios.ts +++ b/packages/web/src/hooks/useTaxiAPI/axios.ts @@ -1,6 +1,6 @@ import _axios from "axios"; -import { backServer as baseURL } from "loadenv"; +import { backServer as baseURL } from "@/tools/loadenv"; const axios = _axios.create({ baseURL, withCredentials: true }); diff --git a/src/hooks/useTaxiAPI/index.tsx b/packages/web/src/hooks/useTaxiAPI/index.tsx similarity index 100% rename from src/hooks/useTaxiAPI/index.tsx rename to packages/web/src/hooks/useTaxiAPI/index.tsx diff --git a/src/hooks/useTaxiAPI/useAxios.tsx b/packages/web/src/hooks/useTaxiAPI/useAxios.tsx similarity index 95% rename from src/hooks/useTaxiAPI/useAxios.tsx rename to packages/web/src/hooks/useTaxiAPI/useAxios.tsx index 08f09a476..5204bda8d 100644 --- a/src/hooks/useTaxiAPI/useAxios.tsx +++ b/packages/web/src/hooks/useTaxiAPI/useAxios.tsx @@ -4,10 +4,10 @@ import { useHistory, useLocation } from "react-router-dom"; import axios from "./axios"; -import errorAtom from "atoms/error"; +import errorAtom from "@/atoms/error"; import { useSetRecoilState } from "recoil"; -import dayjs, { dayNowClient, syncDayWithServer } from "tools/day"; +import dayjs, { dayNowClient, syncDayWithServer } from "@/tools/day"; export type AxiosOption = { url: string; diff --git a/src/hooks/useTaxiAPI/useQuery.tsx b/packages/web/src/hooks/useTaxiAPI/useQuery.ts similarity index 91% rename from src/hooks/useTaxiAPI/useQuery.tsx rename to packages/web/src/hooks/useTaxiAPI/useQuery.ts index 27dadaba7..7ba9f8959 100644 --- a/src/hooks/useTaxiAPI/useQuery.tsx +++ b/packages/web/src/hooks/useTaxiAPI/useQuery.ts @@ -8,7 +8,10 @@ const wrapUseQuery = (method: Method) => (url: string, data?: any, dep?: [any]): [any, any, boolean] => { const axios = useAxios(); - const [res, setRes] = useState({}); + const [res, setRes] = useState<{ error: any; data: any }>({ + error: null, + data: null, + }); const [loading, setLoading] = useState(true); const latestReqID = useRef(0); diff --git a/src/App.css b/packages/web/src/index.css similarity index 96% rename from src/App.css rename to packages/web/src/index.css index 82aa7f8fa..0fbe70bdc 100644 --- a/src/App.css +++ b/packages/web/src/index.css @@ -4,6 +4,7 @@ font-family: "NanumSquare", "Helvetica", "Arial", sans-serif; font-weight: 300; -webkit-tap-highlight-color: transparent; + -webkit-text-size-adjust: 100%; /* -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; */ } diff --git a/src/index.tsx b/packages/web/src/index.tsx similarity index 63% rename from src/index.tsx rename to packages/web/src/index.tsx index 48b4dda48..ebac3f05f 100644 --- a/src/index.tsx +++ b/packages/web/src/index.tsx @@ -2,16 +2,17 @@ import { Suspense } from "react"; import { CookiesProvider } from "react-cookie"; import { createRoot } from "react-dom/client"; import { BrowserRouter as Router } from "react-router-dom"; +import { ToastContainer } from "react-toastify"; -import Loading from "components/Loading"; -import ModalProvider from "components/Modal/ModalProvider"; -import Skeleton from "components/Skeleton"; -import AlertProvider from "components/Skeleton/AlertProvider"; -import Routes from "components/Skeleton/Routes"; -import SocketToastProvider from "components/Skeleton/SocketToastProvider"; +import Loading from "@/components/Loading"; +import ModalProvider from "@/components/Modal/ModalProvider"; +import Skeleton from "@/components/Skeleton"; +import AlertProvider from "@/components/Skeleton/AlertProvider"; +import Routes from "@/components/Skeleton/Routes"; +import SocketToastProvider from "@/components/Skeleton/SocketToastProvider"; -import "./App.css"; import "./Font.css"; +import "./index.css"; import { RecoilRoot } from "recoil"; @@ -27,6 +28,7 @@ const App = () => ( + diff --git a/src/pages/Addroom/FullParticipation.tsx b/packages/web/src/pages/Addroom/FullParticipation.tsx similarity index 67% rename from src/pages/Addroom/FullParticipation.tsx rename to packages/web/src/pages/Addroom/FullParticipation.tsx index fd76dc38c..ffd8e1080 100644 --- a/src/pages/Addroom/FullParticipation.tsx +++ b/packages/web/src/pages/Addroom/FullParticipation.tsx @@ -1,11 +1,11 @@ import { Link } from "react-router-dom"; -import AdaptiveDiv from "components/AdaptiveDiv"; -import Button from "components/Button"; -import Title from "components/Title"; -import WhiteContainer from "components/WhiteContainer"; +import AdaptiveDiv from "@/components/AdaptiveDiv"; +import Button from "@/components/Button"; +import Title from "@/components/Title"; +import WhiteContainer from "@/components/WhiteContainer"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; const FullParticipation = () => { return ( @@ -13,14 +13,14 @@ const FullParticipation = () => { 방 개설하기 - +
참여할 수 있는 방 개수는{" "} 최대 5개 입니다.
- +
이미 5개의 방에 참여 중이며 방 개설이 제한됩니다. 추가적으로 방 개설을 원하는 경우 참여 중인 방에서 정산을 완료하거나{" "} @@ -30,9 +30,11 @@ const FullParticipation = () => { diff --git a/packages/web/src/pages/Addroom/index.tsx b/packages/web/src/pages/Addroom/index.tsx new file mode 100644 index 000000000..4fa1ed1c7 --- /dev/null +++ b/packages/web/src/pages/Addroom/index.tsx @@ -0,0 +1,256 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import { useCookies } from "react-cookie"; +import { useHistory } from "react-router-dom"; + +import { useEvent2024SpringQuestComplete } from "@/hooks/event/useEvent2024SpringQuestComplete"; +import { + useFetchRecoilState, + useIsLogin, + useValueRecoilState, +} from "@/hooks/useFetchRecoilState"; +import { useAxios } from "@/hooks/useTaxiAPI"; + +import AdaptiveDiv from "@/components/AdaptiveDiv"; +import Button from "@/components/Button"; +import { ModalEvent2024SpringAbuseWarning } from "@/components/ModalPopup"; +import { + OptionDate, + OptionMaxPeople, + OptionName, + OptionPlace, + OptionTime, +} from "@/components/ModalRoomOptions"; +import Title from "@/components/Title"; +import WhiteContainerSuggestLogin from "@/components/WhiteContainer/WhiteContainerSuggestLogin"; +import { MAX_PARTICIPATION } from "@/pages/Myroom"; + +import FullParticipation from "./FullParticipation"; + +import alertAtom from "@/atoms/alert"; +import { useSetRecoilState } from "recoil"; + +import { date2str, getToday, getToday10 } from "@/tools/moment"; +import { randomRoomNameGenerator } from "@/tools/random"; +import regExpTest from "@/tools/regExpTest"; +import theme from "@/tools/theme"; + +const AddRoom = () => { + const axios = useAxios(); + const history = useHistory(); + const [cookies, setCookies] = useCookies(["defaultFromTo"]); + + const onCall = useRef(false); + const today = getToday(); + const today10 = getToday10(); + const [valueName, setName] = useState(""); + const [valuePlace, setPlace] = useState( + cookies?.defaultFromTo?.[0] && cookies?.defaultFromTo?.[1] + ? cookies.defaultFromTo + : [null, null] + ); + const [valueDate, setDate] = useState>>([ + today.year(), + today.month() + 1, + today.date(), + ]); + const [valueMaxPeople, setMaxPeople] = useState(4); + const [valueTime, setTime] = useState([today10.hour(), today10.minute()]); + const [calculatedTime, setCalculatedTime] = useState(null); + const randomRoomName = useMemo(randomRoomNameGenerator, []); + + const setAlert = useSetRecoilState(alertAtom); + const isLogin = useIsLogin(); + const myRooms = useValueRecoilState("myRooms"); + const fetchMyRooms = useFetchRecoilState("myRooms"); + //#region event2024Spring + const event2024SpringQuestComplete = useEvent2024SpringQuestComplete(); + const [isOpenModalEventAbuseWarning, setIsOpenModalEventAbuseWarning] = + useState(false); + //#endregion + + useEffect(() => { + const expirationDate = new Date(); + expirationDate.setFullYear(expirationDate.getFullYear() + 10); + if (valuePlace[0] && valuePlace[1]) { + setCookies("defaultFromTo", valuePlace, { + expires: expirationDate, + }); + } + }, [valuePlace]); + + useEffect(() => { + setCalculatedTime( + new Date( + valueDate[0]!, + valueDate[1]! - 1, + valueDate[2]!, + valueTime[0], + valueTime[1] + ) + ); + }, [valueDate, valueTime]); + + let validatedMsg = null; + if (!valuePlace.every((x: Nullable) => !!x)) { + validatedMsg = "출발지와 도착지를 선택해 주세요"; + } else if (valuePlace[0] === valuePlace[1]) { + validatedMsg = "출발지와 도착지는 달라야 합니다"; + } else if (!valueDate[0] || !valueDate[1] || !valueDate[2]) { + validatedMsg = "날짜를 선택해 주세요"; + } else if (today.isSameOrAfter(calculatedTime)) { + validatedMsg = "현재 시각 이후를 선택해주세요"; + } else if (valueName !== "" && !regExpTest.roomName(valueName)) { + validatedMsg = "사용할 수 없는 방 이름입니다"; + } + + const onClickAdd = async () => { + if (!onCall.current) { + onCall.current = true; + + // #region event2024Spring + let isAgreeOnTermsOfEvent = false; + await axios({ + url: "/events/2024spring/globalState", + method: "get", + onSuccess: (data) => { + if (data.isAgreeOnTermsOfEvent) { + isAgreeOnTermsOfEvent = data.isAgreeOnTermsOfEvent; + } + }, + onError: () => {}, + }); + if (isAgreeOnTermsOfEvent) { + let isFalse = false; + await axios({ + url: "/rooms/create/test", + method: "post", + data: { + from: valuePlace[0], + to: valuePlace[1], + time: calculatedTime!.toISOString(), + maxPartLength: valueMaxPeople, + }, + onSuccess: (data) => { + if (data!.result === false) { + setIsOpenModalEventAbuseWarning(true); + onCall.current = false; + isFalse = true; + return; + } + }, + onError: () => {}, + }); + if (isFalse) return; + } + // #endregion + + // FIXME: "/rooms/create" API가 myRoom을 반환하도록 수정 + await axios({ + url: "/rooms/create", + method: "post", + data: { + name: valueName || randomRoomName, + from: valuePlace[0], + to: valuePlace[1], + time: calculatedTime!.toISOString(), + maxPartLength: valueMaxPeople, + }, + onSuccess: () => { + fetchMyRooms(); + //#region event2024Spring + event2024SpringQuestComplete("firstRoomCreation"); + //#endregion + history.push("/myroom"); + }, + onError: () => setAlert("방 개설에 실패하였습니다."), + }); + onCall.current = false; + } + }; + + return (myRooms?.ongoing.length ?? 0) < MAX_PARTICIPATION ? ( + <> +
+ + + 방 개설하기 + + {isLogin ? ( + <> + + + + + + + + ) : ( + + )} + +
+ {/* #region event2024Spring */} + { + if (data === true) { + setIsOpenModalEventAbuseWarning(data); + await axios({ + url: "/rooms/create", + method: "post", + data: { + name: valueName || randomRoomName, + from: valuePlace[0], + to: valuePlace[1], + time: calculatedTime!.toISOString(), + maxPartLength: valueMaxPeople, + }, + onSuccess: () => { + fetchMyRooms(); + //#region event2024spring + event2024SpringQuestComplete("firstRoomCreation"); + //#endregion + history.push("/myroom"); + }, + onError: () => setAlert("방 개설에 실패하였습니다."), + }); + } else if (data === false) { + setIsOpenModalEventAbuseWarning(data); + } + }} + /> + {/* #endregion */} + + ) : ( + + ); +}; + +export default AddRoom; diff --git a/src/pages/Addroom/langs/en.json b/packages/web/src/pages/Addroom/langs/en.json similarity index 100% rename from src/pages/Addroom/langs/en.json rename to packages/web/src/pages/Addroom/langs/en.json diff --git a/src/pages/Addroom/langs/ko.json b/packages/web/src/pages/Addroom/langs/ko.json similarity index 100% rename from src/pages/Addroom/langs/ko.json rename to packages/web/src/pages/Addroom/langs/ko.json diff --git a/src/pages/Chatting/index.tsx b/packages/web/src/pages/Chatting/index.tsx similarity index 84% rename from src/pages/Chatting/index.tsx rename to packages/web/src/pages/Chatting/index.tsx index be977843b..e8161cb50 100644 --- a/src/pages/Chatting/index.tsx +++ b/packages/web/src/pages/Chatting/index.tsx @@ -1,6 +1,6 @@ import { useParams } from "react-router-dom"; -import Chat from "components/Chat"; +import Chat from "@/components/Chat"; const Chatting = () => { const { roomId } = useParams() as { roomId: string }; diff --git a/src/pages/Error/PageNotFound.tsx b/packages/web/src/pages/Error/PageNotFound.tsx similarity index 91% rename from src/pages/Error/PageNotFound.tsx rename to packages/web/src/pages/Error/PageNotFound.tsx index eb9442f30..b7bd943c0 100644 --- a/src/pages/Error/PageNotFound.tsx +++ b/packages/web/src/pages/Error/PageNotFound.tsx @@ -1,6 +1,6 @@ import { useEffect } from "react"; -import errorAtom from "atoms/error"; +import errorAtom from "@/atoms/error"; import { useSetRecoilState } from "recoil"; const PageNotFound = () => { diff --git a/src/pages/Error/index.tsx b/packages/web/src/pages/Error/index.tsx similarity index 73% rename from src/pages/Error/index.tsx rename to packages/web/src/pages/Error/index.tsx index e91068d90..9edb07b22 100644 --- a/src/pages/Error/index.tsx +++ b/packages/web/src/pages/Error/index.tsx @@ -1,13 +1,13 @@ import { useHistory } from "react-router-dom"; -import Button from "components/Button"; +import Button from "@/components/Button"; -import errorAtom from "atoms/error"; +import errorAtom from "@/atoms/error"; import { useRecoilState } from "recoil"; -import theme from "tools/theme"; +import theme from "@/tools/theme"; -import { ReactComponent as TaxiLogo } from "static/assets/TaxiLogo.svg"; +import { ReactComponent as TaxiLogo } from "@/static/assets/sparcsLogos/TaxiLogo.svg"; const Error = () => { const [error, setError] = useRecoilState(errorAtom); @@ -46,18 +46,22 @@ const Error = () => {
+ + +
+ +
+ STEP 2 +
+
+
+ 달토끼 상점에서 +
+ 아이템을 구매해보세요! +
+
+ +
+
+ 일반응모권, 고급응모권, 랜덤박스도 구매 가능 +
+ 상품이 조기에 품절될 수 있어 선착순 판매 +
+
+ + + + +
+ +
+ STEP 3 +
+
+
+ 보너스 경품 추첨에 +
+ 응모권으로 참여해보세요! +
+
+ +
+
+ 응모권은 퀘스트 달성, 달토끼 상점을 통해 얻음 +
+ 고급응모권은 일반응모권 당첨확률의 5배 +
+
+ + + + + +
+
+ +
EVENT
+
+
+ 10월 13일 아이템 받고 +
+ 경품 추첨 결과도 확인해보세요! +
+
+ +
+
+ 추첨 결과는 인스타그램, Ara, Taxi 홈페이지에 발표 +
+ 달토끼 상점에서 구매한 아이템 실물 상품으로 교환 +
+ +
+
+ + +
EVENT
+
+
+ 지금 스토리 공유하고 +
+ 송편 100개 받아가세요! +
+
+ + + + + +
+
+ + +
+
+ 본 이벤트는 위메이드와 KAIST의 후원으로 진행되었습니다. +
+ +
+