diff --git a/public/public-sale/hourly_bg.svg b/public/public-sale/hourly_bg.svg
new file mode 100644
index 000000000..cc5a32aa9
--- /dev/null
+++ b/public/public-sale/hourly_bg.svg
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Providers/user-context.tsx b/src/Providers/user-context.tsx
index 02e2d234e..fecb79c39 100644
--- a/src/Providers/user-context.tsx
+++ b/src/Providers/user-context.tsx
@@ -9,9 +9,9 @@ import { User } from '@/stores/states/user/types';
import { getReferralByURL } from '@/utils/helpers';
import userServices from '@/services/user';
import ReferralStorage from '@/utils/storage/referral.storage';
-import { getCoinPrices } from '@/services/common';
-import { setCoinPrices } from '@/stores/states/common/reducer';
-import { redirect, useRouter } from 'next/navigation';
+import { getCoinPrices, getConfigs } from '@/services/common';
+import { setCoinPrices, setConfigs } from '@/stores/states/common/reducer';
+import { useRouter } from 'next/navigation';
export interface IUserContext {}
@@ -50,6 +50,12 @@ export const UserProvider: React.FC = ({
dispatch(setCoinPrices(coinPrices));
};
+ const fetchConfigs = async () => {
+ const configs = await getConfigs();
+ if (!configs) return;
+ dispatch(setConfigs(configs));
+ };
+
const contextValues = useMemo((): IUserContext => {
return {};
}, []);
@@ -70,8 +76,10 @@ export const UserProvider: React.FC = ({
React.useEffect(() => {
fetchCoinPrices();
+ fetchConfigs();
setInterval(() => {
fetchCoinPrices();
+ fetchConfigs();
}, 60 * 1000);
}, []);
diff --git a/src/modules/PublicSale/aboveTheFold/index.tsx b/src/modules/PublicSale/aboveTheFold/index.tsx
index 4d2beecb7..556b7e36f 100644
--- a/src/modules/PublicSale/aboveTheFold/index.tsx
+++ b/src/modules/PublicSale/aboveTheFold/index.tsx
@@ -9,9 +9,9 @@ import { getVCInformation } from '@/services/player-share';
import { VCInfo } from '@/interfaces/vc';
import LeaderBoardVisual from '@/modules/PublicSale/leaderBoardVisual';
import Activities from '@/modules/PublicSale/activities';
-import LeaderBoardSwitch from '@/modules/PublicSale/leaderBoardSwitch';
import useWindowSize from '@/hooks/useWindowSize';
import DailyReward from '@/modules/PublicSale/dailyReward';
+import HourlyReward from '@/modules/PublicSale/hourlyReward';
const AboveTheFold = () => {
const { setPlay } = useAnimationStore();
@@ -53,6 +53,7 @@ const AboveTheFold = () => {
{/* */}
+
{/* */}
{mobileScreen && }
diff --git a/src/modules/PublicSale/aboveTheFold/styles.module.scss b/src/modules/PublicSale/aboveTheFold/styles.module.scss
index 6c8a944f9..5e9e5616d 100644
--- a/src/modules/PublicSale/aboveTheFold/styles.module.scss
+++ b/src/modules/PublicSale/aboveTheFold/styles.module.scss
@@ -5,7 +5,7 @@
width: 100%;
:global {
- --top-spacing: 24px;
+ --top-spacing: 20px;
--item-spacing: 12px;
}
diff --git a/src/modules/PublicSale/activities/index.tsx b/src/modules/PublicSale/activities/index.tsx
index 3256acf65..fa9aa3e68 100644
--- a/src/modules/PublicSale/activities/index.tsx
+++ b/src/modules/PublicSale/activities/index.tsx
@@ -44,6 +44,8 @@ export interface GameItemProps {
src: string;
ctas?: ICTA[];
type: ActivityType;
+ startTime?: string;
+ endTime?: string;
}
const Activities = React.memo(() => {
@@ -92,8 +94,8 @@ Good luck and have fun!
],
desc:
'NakaChain is a low-cost and lightning-fast Bitcoin Layer 2 blockchain designed for DeFi apps, enabling the payment of gas fees in Bitcoin. It’s powered by BVM with these modules: Bitcoin for security, Polygon for data availability, and Optimism for execution.' +
- " On the second day of awesomeness, challenge yourself to dominate the market by trading futures on BRC-20 tokens' prices. Every two hours, the top gainer will earn $50 in Bitcoin.\n" +
- ' Total rewards: $1,000 ',
+ " On the second day of awesomeness, challenge yourself to dominate the market by trading futures on BRC-20 tokens' prices. Every four hours, the top gainer will earn $50 in Bitcoin.\n"
+ // ' Total rewards: $1,000 ',
},
{
key: 2,
@@ -243,6 +245,8 @@ Good luck and have fun!
const isDisable = item.key > currentDay.diffDay;
const title = isDisable ? item.title : item.title;
+ const isRunningNaka = expandIndex === item.key && item.key === ActivityType.Day2
+
return (
{({ isExpanded }) => (
@@ -263,8 +267,11 @@ Good luck and have fun!
>
+ {item.key < currentDay.diffDay && (
+ Happening Now
+ )}
{item.key === currentDay.diffDay && (
- Happening Now
+ New unlocked
)}
@@ -297,9 +304,7 @@ Good luck and have fun!
className={styles.itemWrapper_desc}
dangerouslySetInnerHTML={{ __html: item.desc }}
/>
- {currentDay.diffDay === expandIndex &&
- expandIndex === item.key &&
- item.key === 1 && }
+ {isRunningNaka && }
{item.ctas?.map(renderCta)}
diff --git a/src/modules/PublicSale/activities/styles.module.scss b/src/modules/PublicSale/activities/styles.module.scss
index 88efb6ba2..884b18dd2 100644
--- a/src/modules/PublicSale/activities/styles.module.scss
+++ b/src/modules/PublicSale/activities/styles.module.scss
@@ -47,6 +47,26 @@
padding-bottom: 0 !important;
}
+ &_happening {
+ color: black;
+ padding: 4px 12px;
+ font-size: 10px;
+ font-weight: 400;
+ border-radius: 100px;
+ margin-right: 4px;
+ background: linear-gradient(90deg, #00F5A0 0%, #00D9F5 100%);
+ }
+
+ &_unlocked {
+ color: black;
+ padding: 4px 12px;
+ font-size: 10px;
+ font-weight: 400;
+ border-radius: 100px;
+ margin-right: 4px;
+ background: linear-gradient(90deg, #FFE259 0%, #FFA751 100%);
+ }
+
&_title {
color: #ffffff;
font-size: 14px;
@@ -59,17 +79,6 @@
max-width: 214px;
}
- span {
- color: black;
- padding: 4px 12px;
- font-size: 10px;
- font-weight: 400;
- border-radius: 100px;
- background: #fa4e0e;
- margin-right: 4px;
- background: linear-gradient(90deg, #00F5A0 0%, #00D9F5 100%);
- }
-
@include is-mobile {
font-size: 14px;
}
diff --git a/src/modules/PublicSale/dailyReward/styles.module.scss b/src/modules/PublicSale/dailyReward/styles.module.scss
index cf0aafa6d..4517ccc36 100644
--- a/src/modules/PublicSale/dailyReward/styles.module.scss
+++ b/src/modules/PublicSale/dailyReward/styles.module.scss
@@ -2,7 +2,7 @@
position: absolute;
flex-direction: column;
top: var(--top-spacing);
- left: 24px;
+ left: 20px;
gap: 16px;
min-width: 280px;
max-width: 280px;
diff --git a/src/modules/PublicSale/hourlyReward/index.tsx b/src/modules/PublicSale/hourlyReward/index.tsx
new file mode 100644
index 000000000..d9970a3af
--- /dev/null
+++ b/src/modules/PublicSale/hourlyReward/index.tsx
@@ -0,0 +1,73 @@
+import s from './styles.module.scss';
+import { Flex, Text } from '@chakra-ui/react';
+import dayjs from 'dayjs';
+import React, { useEffect, useMemo, useState } from 'react';
+import { getPublicSaleProgram, IPublicSalePrograme } from '@/services/public-sale';
+import HourlyRewardButton from '@/modules/PublicSale/hourlyRewardButton';
+import { useAppSelector } from '@/stores/hooks';
+import { commonSelector } from '@/stores/states/common/selector';
+import BigNumber from 'bignumber.js';
+import { PUBLIC_SALE_START } from '@/modules/Whitelist';
+import { formatCurrency } from '@/utils/format';
+import { MIN_DECIMAL } from '@/constants/constants';
+
+const HourlyReward = () => {
+ const [isLoading, setIsLoading] = useState(true);
+ const [isEnd, setIsEnd] = React.useState(false);
+ const [programeInfo, setProgrameInfo] = useState();
+ const configs = useAppSelector(commonSelector).configs;
+
+ useEffect(() => {
+ getProgramInfo();
+ }, []);
+
+ const getProgramInfo = async () => {
+ try {
+ const res = await getPublicSaleProgram();
+ setProgrameInfo(res);
+ } catch (e) {
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const currentDay = React.useMemo(() => {
+ const diffDay = new BigNumber(
+ dayjs.utc(PUBLIC_SALE_START).diff(dayjs.utc(), 'days'),
+ )
+ .absoluteValue()
+ .toNumber();
+ return {
+ // step: DAYS.length > diffDay ? DAYS[diffDay] : DAYS[DAYS.length - 1],
+ diffDay,
+ };
+ }, []);
+
+ const REWARDS = useMemo(() => {
+ if(configs) {
+ if(configs['naka']?.bvm_halvings) {
+ const res = JSON.parse(configs['naka']?.bvm_halvings);
+ return Object.values(res);
+ }
+ }
+ return [];
+ }, [configs]);
+
+ const currentHourReward: number = useMemo(() => {
+ return (REWARDS[currentDay.diffDay] as number) / 24;
+ }, [currentDay, REWARDS])
+
+ return (
+
+
+ Hourly Reward
+
+ {formatCurrency(currentHourReward, 0, 0, 'BTC', false)} BVM
+
+
+
+
+ )
+}
+
+export default HourlyReward;
diff --git a/src/modules/PublicSale/hourlyReward/styles.module.scss b/src/modules/PublicSale/hourlyReward/styles.module.scss
new file mode 100644
index 000000000..030070465
--- /dev/null
+++ b/src/modules/PublicSale/hourlyReward/styles.module.scss
@@ -0,0 +1,48 @@
+.container {
+ position: absolute;
+ flex-direction: column;
+ top: var(--top-spacing);
+ right: 20px;
+ gap: 16px;
+ //min-width: 280px;
+ //max-width: 280px;
+ //border: 1px solid #ffffff1a;
+ background: linear-gradient(180deg, #FDF6EA 0%, #FCF2DC 27%, #FBECC9 72.17%, #FBECC9 100%);
+ padding: 12px;
+ width: fit-content;
+
+ .title {
+ font-size: 12px;
+ line-height: 100%;
+ font-weight: 400;
+ color: #894D1C;
+ padding-top: 1px;
+ }
+
+ .time {
+ //margin-top: 10px;
+ font-size: 14px;
+ line-height: 100%;
+ font-weight: 500;
+ text-transform: uppercase;
+ background: linear-gradient(180deg, #DF7E2E -12.5%, #894D1C 100%);
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ background-clip: text;
+
+ p {
+ font-size: inherit !important;
+ font-weight: inherit !important;
+ white-space: pre;
+ }
+
+ @include is-mobile {
+ font-size: 14px;
+ }
+ }
+
+ @include is-mobile {
+ max-width: unset;
+ width: calc(100% - 50px);
+ }
+}
diff --git a/src/modules/PublicSale/hourlyRewardButton/index.tsx b/src/modules/PublicSale/hourlyRewardButton/index.tsx
new file mode 100644
index 000000000..9a768265a
--- /dev/null
+++ b/src/modules/PublicSale/hourlyRewardButton/index.tsx
@@ -0,0 +1,55 @@
+import { Center, Flex, Text } from '@chakra-ui/react';
+import s from './styles.module.scss';
+import React, { useMemo } from 'react';
+import cx from 'clsx';
+import dayjs from 'dayjs';
+import Countdown from '@/modules/Whitelist/stepAirdrop/Countdown';
+
+const HourlyRewardButton = ({ className }: any) => {
+ const [isEnd, setIsEnd] = React.useState(false);
+
+ const hourlyEndTime = useMemo(() => {
+ let res = dayjs.utc().set('minute', 30);
+ res = res.set('second', 0);
+ if (dayjs().utc().isAfter(res)) {
+ res = res.set('hour', res.get('hour') + 1);
+ }
+ if(isEnd) {
+ setIsEnd(false);
+ }
+
+ return res.toString();
+ }, [isEnd]);
+
+ // console.log('dayjs.utc()', dayjs.utc().toString());
+ // console.log('hourlyEndTime', hourlyEndTime);
+
+ return (
+
+
+
+
+
+
+
+ End in
+ setIsEnd(true)}
+ type={"column"}
+ showColon={true}
+ />
+
+
+
+ );
+};
+
+export default HourlyRewardButton;
diff --git a/src/modules/PublicSale/hourlyRewardButton/styles.module.scss b/src/modules/PublicSale/hourlyRewardButton/styles.module.scss
new file mode 100644
index 000000000..16cf005af
--- /dev/null
+++ b/src/modules/PublicSale/hourlyRewardButton/styles.module.scss
@@ -0,0 +1,186 @@
+$time:10s; // including 1 flip
+
+.container {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ transition: all 0.3s;
+ position: relative;
+ background: url('/public-sale/hourly_bg.svg');
+ background-size: cover;
+ background-repeat: no-repeat;
+ //cursor: pointer;
+ width: 165px;
+ height: 99px;
+ padding: 6px;
+
+ .title {
+ font-size: 12px;
+ line-height: 100%;
+ font-weight: 400;
+ color: #894D1C;
+ padding-top: 1px;
+ }
+
+ .icon {
+ width: 40px;
+ height: 40px;
+ background: url('/public-sale/rwbn_2.png');
+ background-size: cover;
+ background-repeat: no-repeat;
+ cursor: pointer;
+ align-items: flex-end;
+ }
+
+ .text_text {
+ display: block;
+ margin-bottom: 4px;
+ }
+
+ .timeWrapper {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ //left: 0;
+ //right: 0;
+ //margin-left: auto;
+ //margin-right: auto;
+ width: 100%;
+ //margin-bottom: 8px;
+ }
+
+ .time {
+ //margin-top: 10px;
+ font-size: 12px;
+ line-height: 100%;
+ font-weight: 400;
+ text-transform: uppercase;
+ width: fit-content;
+ color: #894D1C;
+
+ p {
+ font-size: inherit !important;
+ font-weight: inherit !important;
+ white-space: pre;
+ color: inherit;
+ background: #FBECC9;
+ padding: 4px;
+ min-width: unset;
+ width: 24px;
+ height: 24px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding-top: 5px;
+ //> span {
+ // background: #FBECC9;
+ // padding: 4px;
+ //}
+ }
+
+ > div {
+ gap: 4px;
+ align-items: center;
+ > div {
+ span {
+ display: none;
+ }
+ }
+ }
+
+ @include is-mobile {
+ font-size: 14px;
+ }
+
+ &.claimable {
+ background: linear-gradient(90deg, #00F5A0 0%, #00D9F5 100%);
+ -webkit-background-clip: text; /* clip the background to the text inside the tag*/
+ -webkit-text-fill-color: transparent;
+ }
+ }
+
+ .hourglassWrapper {
+ transform: scale(0.2);
+ margin-top: 25px;
+ }
+
+ :global {
+ .hourglass {
+ animation:flip $time ease-in-out infinite;
+ border-bottom:solid 1vmin #630;
+ border-top:solid 1vmin #630;
+ left:50%;
+ margin-left:-6vmin;
+ margin-top:-11vmin;
+ padding:0 1vmin;
+ position:fixed;
+ top:50%;
+ .top, .bottom {
+ background-color:#def;
+ box-shadow:0 0 1vmin 1vmin #bcd inset;
+ height:10vmin;
+ overflow:hidden;
+ position:relative;
+ width:10vmin;
+ }
+ .top {
+ border-radius:0 0 50% 50%;
+ &:before {
+ animation:top $time linear infinite;
+ background-color:#fc6;
+ border-radius:50%;
+ content:"";
+ display:block;
+ height:10vmin;
+ left:0;
+ position:absolute;
+ top:0;
+ transform:translateY(50%);
+ width:10vmin;
+ }
+ &:after {
+ animation:top-drip $time linear infinite;
+ background-color:#fc6;
+ content:"";
+ display:block;
+ height:100%;
+ left:45%;
+ position:absolute;
+ top:0;
+ transform:translateY(100%);
+ width:10%;
+ }
+ }
+ .bottom {
+ border-radius:50% 50% 0 0;
+ &:before {
+ animation:bottom $time linear infinite;
+ background-color:#fc6;
+ border-radius:50%;
+ content:"";
+ display:block;
+ height:10vmin;
+ left:0;
+ position:absolute;
+ top:0;
+ transform:translateY(100%);
+ width:10vmin;
+ }
+ &:after {
+ animation:bottom-drip $time linear infinite;
+ background-color:#fc6;
+ content:"";
+ display:block;
+ height:100%;
+ left:45%;
+ position:absolute;
+ top:0;
+ width:10%;
+ }
+ }
+ }
+
+
+ }
+}
diff --git a/src/modules/PublicSale/rewardButton/VerifyRewardDailyModal/index.tsx b/src/modules/PublicSale/rewardButton/VerifyRewardDailyModal/index.tsx
index 8d2e722d6..5637ba9b7 100644
--- a/src/modules/PublicSale/rewardButton/VerifyRewardDailyModal/index.tsx
+++ b/src/modules/PublicSale/rewardButton/VerifyRewardDailyModal/index.tsx
@@ -68,7 +68,7 @@ const VerifyRewardDailyModal = ({
diff --git a/src/modules/PublicSale/rewardButton/index.tsx b/src/modules/PublicSale/rewardButton/index.tsx
index 84525abec..77b07bb35 100644
--- a/src/modules/PublicSale/rewardButton/index.tsx
+++ b/src/modules/PublicSale/rewardButton/index.tsx
@@ -36,8 +36,6 @@ import { PUBLIC_SALE_START } from '@/modules/Whitelist';
import { commonSelector } from '@/stores/states/common/selector';
import useWindowSize from '@/hooks/useWindowSize';
-const REWARD_DAILY = JSON.parse("{\"2024-01-30\":100000,\"2024-01-31\":50000,\"2024-02-01\":50000,\"2024-02-02\":50000,\"2024-02-03\":50000,\"2024-02-04\":50000,\"2024-02-05\":50000}");
-
const RaffleButton = ({ className }: any) => {
const { isOpen, onOpen, onClose } = useDisclosure();
const [isEnd, setIsEnd] = React.useState(false);
@@ -52,6 +50,7 @@ const RaffleButton = ({ className }: any) => {
const [authenCode, setAuthenCode] = useState();
const [showManualCheck, setShowManualCheck] = useState(false);
const needReload = useAppSelector(commonSelector).needReload;
+ const configs = useAppSelector(commonSelector).configs;
const { mobileScreen } = useWindowSize();
const currentDay = React.useMemo(() => {
@@ -67,20 +66,19 @@ const RaffleButton = ({ className }: any) => {
}, []);
const REWARDS = useMemo(() => {
- return Object.values(REWARD_DAILY);
- }, [REWARD_DAILY]);
+ if(configs) {
+ if(configs['naka']?.bvm_halvings) {
+ const res = JSON.parse(configs['naka']?.bvm_halvings);
+ return Object.values(res);
+ }
+ }
+ return [];
+ }, [configs]);
const currentDayReward = useMemo(() => {
return REWARDS[currentDay.diffDay];
}, [currentDay, REWARDS])
- console.log('dailyReward', dailyReward);
- console.log('user', user);
- console.log('currentDay', currentDay);
- console.log('REWARDS', REWARDS);
- console.log('currentDayReward', currentDayReward);
- console.log('=====')
-
useEffect(() => {
getProgramInfo();
diff --git a/src/modules/Whitelist/stepAirdrop/Countdown/index.tsx b/src/modules/Whitelist/stepAirdrop/Countdown/index.tsx
index c880a9e9d..73c39033c 100644
--- a/src/modules/Whitelist/stepAirdrop/Countdown/index.tsx
+++ b/src/modules/Whitelist/stepAirdrop/Countdown/index.tsx
@@ -12,6 +12,7 @@ interface IProps {
onRefreshEnd?: () => void;
type?: 'row' | 'column',
hideZeroHour?: boolean
+ showColon?: boolean
}
const Countdown: React.FC = ({
@@ -21,7 +22,8 @@ const Countdown: React.FC = ({
onRefreshEnd,
type = 'row',
isHideSecond,
- hideZeroHour = false
+ hideZeroHour = false,
+ showColon = false
}: IProps): React.ReactElement => {
const refCallEnd = useRef(false);
const {
@@ -78,25 +80,44 @@ const Countdown: React.FC = ({
(
{showDay && (
-
- {days}
- DAYS
-
+ <>
+
+ {days}
+ DAYS
+
+ {
+ showColon && ':'
+ }
+ >
)}
{!!hideZeroHour && !hours && (
-
- {hours}
- HOURS
-
+ <>
+
+ {hours}
+ HOURS
+
+ {
+ showColon && ':'
+ }
+ >
+
)}
{minutes}
MINS
- {!isHideSecond &&
- {seconds}
- SECONDS
- }
+ {!isHideSecond && (
+ <>
+ {
+ showColon && ':'
+ }
+
+ {seconds}
+ SECONDS
+
+ >
+ )
+ }
)
)
diff --git a/src/services/common.ts b/src/services/common.ts
index 89b0f564c..8a7721e18 100644
--- a/src/services/common.ts
+++ b/src/services/common.ts
@@ -15,6 +15,16 @@ const getCoinPrices = async (): Promise => {
}
};
+const getConfigs = async (): Promise => {
+ try {
+ const res = (await apiClient.get('/configs')) as any;
+ return res;
+ } catch (error) {
+ return undefined;
+ }
+};
+
export {
- getCoinPrices
+ getCoinPrices,
+ getConfigs
}
diff --git a/src/stores/states/common/reducer.ts b/src/stores/states/common/reducer.ts
index 5aa5f2a9a..d16f64c06 100644
--- a/src/stores/states/common/reducer.ts
+++ b/src/stores/states/common/reducer.ts
@@ -8,6 +8,7 @@ const initialState: CommonState = {
[Coin.ETH]: '0',
[Coin.TIA]: '0',
} as any,
+ configs: null,
leaderBoardMode: 1,
needCheckDeposit: false,
animatedLatestContributors: [],
@@ -23,6 +24,9 @@ const slice = createSlice({
setCoinPrices: (state, action) => {
state.coinPrices = action.payload;
},
+ setConfigs: (state, action) => {
+ state.configs = action.payload;
+ },
setLeaderBoardMode: (state, action) => {
state.leaderBoardMode = action.payload;
},
@@ -38,6 +42,7 @@ const slice = createSlice({
export const {
requestReload,
setCoinPrices,
+ setConfigs,
setLeaderBoardMode,
setNeedCheckDeposit,
setAnimatedLatestContributors
diff --git a/src/stores/states/common/types.ts b/src/stores/states/common/types.ts
index 3ef527a0e..2e6c696e8 100644
--- a/src/stores/states/common/types.ts
+++ b/src/stores/states/common/types.ts
@@ -13,6 +13,7 @@ export enum Coin {
export interface CommonState {
needReload: number;
coinPrices: CoinPrices,
+ configs: any,
leaderBoardMode: 0 | 1;
needCheckDeposit: boolean;
animatedLatestContributors: ILeaderBoardPoint[];
diff --git a/src/styles/_global.scss b/src/styles/_global.scss
index 2859eced2..e9a203d34 100644
--- a/src/styles/_global.scss
+++ b/src/styles/_global.scss
@@ -38,3 +38,80 @@ input[type='number'] {
.anim-scale {
width: 100%;
}
+
+
+@keyframes flip {
+ 0%, 45% {
+ transform:rotate(0);
+ }
+ 50%, 95% {
+ transform:rotate(180deg);
+ }
+ 100% {
+ transform:rotate(360deg);
+ }
+}
+
+@keyframes bottom {
+ 0% {
+ transform:translateY(100%);
+ }
+ 50% {
+ transform:translateY(50%);
+ }
+ 51% {
+ transform:translateY(-50%);
+ }
+ 100% {
+ transform:translateY(-100%);
+ }
+}
+
+@keyframes top {
+ 0% {
+ transform:translateY(50%);
+ }
+ 50% {
+ transform:translateY(100%);
+ }
+ 51% {
+ transform:translateY(-100%);
+ }
+ 100% {
+ transform:translateY(-50%);
+ }
+}
+
+@keyframes bottom-drip {
+ 0% {
+ left:45%;
+ transform:translateY(-100%);
+ width:10%;
+ }
+ 5% {
+ transform:translateY(0);
+ }
+ 45%, 100% {
+ left:50%;
+ transform:translateY(0);
+ width:0;
+ }
+}
+
+@keyframes top-drip {
+ 0%, 50% {
+ left:45%;
+ transform:translateY(100%);
+ width:10%;
+ }
+ 55% {
+ left:45%;
+ transform:translateY(0);
+ width:10%;
+ }
+ 100% {
+ left:50%;
+ transform:translateY(0);
+ width:0;
+ }
+}