From 657c6526574a8cbfc257535e19e752ca6db79fb0 Mon Sep 17 00:00:00 2001 From: louiscklaw Date: Sun, 8 Jun 2025 18:40:35 +0800 Subject: [PATCH] updtae Demo2FaExample, --- .../pages/Demo2FaExample/AppPages/Tab1.jsx | 4 +- .../pages/Demo2FaExample/AppPages/Tab2.jsx | 2 +- .../CurrentWeather/WeatherProperty.tsx | 0 .../CurrentWeather/index.tsx | 0 .../SkeletonDashboard/index.tsx | 0 .../components/Keypad.module.scss | 5 + .../Demo2FaExample/components/Keypad.tsx | 118 +++++++++ .../components/KeypadButton.module.scss | 30 +++ .../components/KeypadButton.tsx | 31 +++ .../components/KeypadInput.module.scss | 38 +++ .../Demo2FaExample/components/KeypadInput.tsx | 28 ++ .../components/KeypadInputs.tsx | 50 ++++ .../mobile/src/pages/Demo2FaExample/index.tsx | 31 +-- .../Demo2FaExample/pages/Home.module.scss | 30 +++ .../src/pages/Demo2FaExample/pages/Home.tsx | 135 ++++++++++ .../src/pages/Demo2FaExample/style.scss | 103 -------- .../pages/Demo2FaExample/theme/variables.scss | 249 ++++++++++++++++++ 17 files changed, 723 insertions(+), 131 deletions(-) rename 03_source/mobile/src/pages/Demo2FaExample/{components => TestComponents}/CurrentWeather/WeatherProperty.tsx (100%) rename 03_source/mobile/src/pages/Demo2FaExample/{components => TestComponents}/CurrentWeather/index.tsx (100%) rename 03_source/mobile/src/pages/Demo2FaExample/{components => TestComponents}/SkeletonDashboard/index.tsx (100%) create mode 100644 03_source/mobile/src/pages/Demo2FaExample/components/Keypad.module.scss create mode 100644 03_source/mobile/src/pages/Demo2FaExample/components/Keypad.tsx create mode 100644 03_source/mobile/src/pages/Demo2FaExample/components/KeypadButton.module.scss create mode 100644 03_source/mobile/src/pages/Demo2FaExample/components/KeypadButton.tsx create mode 100644 03_source/mobile/src/pages/Demo2FaExample/components/KeypadInput.module.scss create mode 100644 03_source/mobile/src/pages/Demo2FaExample/components/KeypadInput.tsx create mode 100644 03_source/mobile/src/pages/Demo2FaExample/components/KeypadInputs.tsx create mode 100644 03_source/mobile/src/pages/Demo2FaExample/pages/Home.module.scss create mode 100644 03_source/mobile/src/pages/Demo2FaExample/pages/Home.tsx delete mode 100644 03_source/mobile/src/pages/Demo2FaExample/style.scss create mode 100644 03_source/mobile/src/pages/Demo2FaExample/theme/variables.scss diff --git a/03_source/mobile/src/pages/Demo2FaExample/AppPages/Tab1.jsx b/03_source/mobile/src/pages/Demo2FaExample/AppPages/Tab1.jsx index 54ab2b9..a24d76a 100644 --- a/03_source/mobile/src/pages/Demo2FaExample/AppPages/Tab1.jsx +++ b/03_source/mobile/src/pages/Demo2FaExample/AppPages/Tab1.jsx @@ -14,9 +14,9 @@ import { import { Geolocation } from '@capacitor/geolocation'; import { useEffect, useState } from 'react'; -import { SkeletonDashboard } from '../components/SkeletonDashboard'; +import { SkeletonDashboard } from '../TestComponents/SkeletonDashboard'; import { chevronBackOutline, refreshOutline } from 'ionicons/icons'; -import { CurrentWeather } from '../components/CurrentWeather'; +import { CurrentWeather } from '../TestComponents/CurrentWeather'; function Tab1() { const router = useIonRouter(); diff --git a/03_source/mobile/src/pages/Demo2FaExample/AppPages/Tab2.jsx b/03_source/mobile/src/pages/Demo2FaExample/AppPages/Tab2.jsx index 216544f..c258179 100644 --- a/03_source/mobile/src/pages/Demo2FaExample/AppPages/Tab2.jsx +++ b/03_source/mobile/src/pages/Demo2FaExample/AppPages/Tab2.jsx @@ -10,7 +10,7 @@ import { IonToolbar, } from '@ionic/react'; import { useState } from 'react'; -import { CurrentWeather } from '../components/CurrentWeather'; +import { CurrentWeather } from '../TestComponents/CurrentWeather'; function Tab2() { const [search, setSearch] = useState(''); diff --git a/03_source/mobile/src/pages/Demo2FaExample/components/CurrentWeather/WeatherProperty.tsx b/03_source/mobile/src/pages/Demo2FaExample/TestComponents/CurrentWeather/WeatherProperty.tsx similarity index 100% rename from 03_source/mobile/src/pages/Demo2FaExample/components/CurrentWeather/WeatherProperty.tsx rename to 03_source/mobile/src/pages/Demo2FaExample/TestComponents/CurrentWeather/WeatherProperty.tsx diff --git a/03_source/mobile/src/pages/Demo2FaExample/components/CurrentWeather/index.tsx b/03_source/mobile/src/pages/Demo2FaExample/TestComponents/CurrentWeather/index.tsx similarity index 100% rename from 03_source/mobile/src/pages/Demo2FaExample/components/CurrentWeather/index.tsx rename to 03_source/mobile/src/pages/Demo2FaExample/TestComponents/CurrentWeather/index.tsx diff --git a/03_source/mobile/src/pages/Demo2FaExample/components/SkeletonDashboard/index.tsx b/03_source/mobile/src/pages/Demo2FaExample/TestComponents/SkeletonDashboard/index.tsx similarity index 100% rename from 03_source/mobile/src/pages/Demo2FaExample/components/SkeletonDashboard/index.tsx rename to 03_source/mobile/src/pages/Demo2FaExample/TestComponents/SkeletonDashboard/index.tsx diff --git a/03_source/mobile/src/pages/Demo2FaExample/components/Keypad.module.scss b/03_source/mobile/src/pages/Demo2FaExample/components/Keypad.module.scss new file mode 100644 index 0000000..38c85a2 --- /dev/null +++ b/03_source/mobile/src/pages/Demo2FaExample/components/Keypad.module.scss @@ -0,0 +1,5 @@ +.keypad { + bottom: 0; + position: absolute; + width: 100%; +} diff --git a/03_source/mobile/src/pages/Demo2FaExample/components/Keypad.tsx b/03_source/mobile/src/pages/Demo2FaExample/components/Keypad.tsx new file mode 100644 index 0000000..db88125 --- /dev/null +++ b/03_source/mobile/src/pages/Demo2FaExample/components/Keypad.tsx @@ -0,0 +1,118 @@ +import { IonRow } from "@ionic/react"; +import styles from "./Keypad.module.scss"; +import KeypadButton from "./KeypadButton"; + +const Keypad = (props: any): JSX.Element => { + const { activeIndex, handleClick, handleRemove, amount, correct } = props; + + const keypadButtons = [ + [ + { + value: "1", + handleClick: () => handleClick(activeIndex, 1), + small: false, + remove: false, + }, + { + value: "2", + handleClick: () => handleClick(activeIndex, 2), + small: false, + remove: false, + }, + { + value: "3", + handleClick: () => handleClick(activeIndex, 3), + small: false, + remove: false, + }, + ], + [ + { + value: "4", + handleClick: () => handleClick(activeIndex, 4), + small: false, + remove: false, + }, + { + value: "5", + handleClick: () => handleClick(activeIndex, 5), + small: false, + remove: false, + }, + { + value: "6", + handleClick: () => handleClick(activeIndex, 6), + small: false, + remove: false, + }, + ], + [ + { + value: "7", + handleClick: () => handleClick(activeIndex, 7), + small: false, + remove: false, + }, + { + value: "8", + handleClick: () => handleClick(activeIndex, 8), + small: false, + remove: false, + }, + { + value: "9", + handleClick: () => handleClick(activeIndex, 9), + small: false, + remove: false, + }, + ], + [ + { + value: "Resend", + handleClick: () => handleClick(activeIndex, 1), + small: true, + remove: false, + }, + { + value: "0", + handleClick: () => handleClick(activeIndex, 2), + small: false, + remove: false, + }, + { + value: "", + handleClick: () => handleRemove(), + small: true, + remove: true, + }, + ], + ]; + + return ( +
+ {keypadButtons.map((keypadRow, index) => { + const isDisabled = parseInt(activeIndex) === parseInt(amount); + + return ( + + {keypadRow.map((button, index2) => { + return ( + + ); + })} + + ); + })} +
+ ); +}; + +export default Keypad; diff --git a/03_source/mobile/src/pages/Demo2FaExample/components/KeypadButton.module.scss b/03_source/mobile/src/pages/Demo2FaExample/components/KeypadButton.module.scss new file mode 100644 index 0000000..400a722 --- /dev/null +++ b/03_source/mobile/src/pages/Demo2FaExample/components/KeypadButton.module.scss @@ -0,0 +1,30 @@ +.logo { + height: 4rem; + width: auto; +} + +.keypadButton { + --background: none; + --color: black; + font-size: 2rem; + font-weight: 700; + --outline: none; + --border: none; + --box-shadow: none; + padding: none; + margin: none; + --background-hover: rgb(245, 245, 245) !important; + --background-focused: rgb(245, 245, 245) !important; + --background-activated: rgb(245, 245, 245) !important; +} + +.smallKeypadButton { + font-size: 1.4rem; + margin-top: 1rem; +} + +.keypad { + bottom: 0; + position: absolute; + width: 100%; +} diff --git a/03_source/mobile/src/pages/Demo2FaExample/components/KeypadButton.tsx b/03_source/mobile/src/pages/Demo2FaExample/components/KeypadButton.tsx new file mode 100644 index 0000000..320a13c --- /dev/null +++ b/03_source/mobile/src/pages/Demo2FaExample/components/KeypadButton.tsx @@ -0,0 +1,31 @@ +import { IonButton, IonCol, IonIcon } from "@ionic/react"; +import { backspaceOutline } from "ionicons/icons"; +import styles from "./KeypadButton.module.scss"; + +const KeypadButton = (props: any): JSX.Element => { + const { + small, + value, + remove, + handleClick, + isDisabled = false, + correct, + } = props; + + return ( + + + {!remove && value} + {remove && } + + + ); +}; + +export default KeypadButton; diff --git a/03_source/mobile/src/pages/Demo2FaExample/components/KeypadInput.module.scss b/03_source/mobile/src/pages/Demo2FaExample/components/KeypadInput.module.scss new file mode 100644 index 0000000..c54a1b8 --- /dev/null +++ b/03_source/mobile/src/pages/Demo2FaExample/components/KeypadInput.module.scss @@ -0,0 +1,38 @@ +.keypadInput { + display: flex; + flex-direction: row; + justify-content: center; + align-content: center; + align-items: center; + font-size: 2.5rem; + width: 100%; + min-height: 2.5rem; + background-color: rgb(245, 245, 245); + border-radius: 4px; + color: rgb(207, 207, 207); + transition: 0.2s linear; +} + +.active { + background-color: rgba(26, 150, 251, 0.2); + border: 0 !important; + color: white !important; + transition: 0.2s linear; +} + +.filled { + color: rgb(151, 151, 151); + transition: 0.2s linear; +} + +.incorrect { + background-color: rgba(251, 26, 26, 0.2); + color: rgb(218, 67, 67); + transition: 0.2s linear; +} + +.correct { + background-color: rgba(26, 251, 120, 0.2); + color: rgb(67, 218, 112); + transition: 0.2s linear; +} diff --git a/03_source/mobile/src/pages/Demo2FaExample/components/KeypadInput.tsx b/03_source/mobile/src/pages/Demo2FaExample/components/KeypadInput.tsx new file mode 100644 index 0000000..5b6f532 --- /dev/null +++ b/03_source/mobile/src/pages/Demo2FaExample/components/KeypadInput.tsx @@ -0,0 +1,28 @@ +import { IonCol } from "@ionic/react"; +import styles from "./KeypadInput.module.scss"; + +const KeypadInput = (props: any): JSX.Element => { + const { + value, + isActive = false, + isFilled = false, + incorrect, + correct, + } = props; + + return ( + +
+ {value} + {!isFilled && "0"} +
+
+ ); +}; + +export default KeypadInput; diff --git a/03_source/mobile/src/pages/Demo2FaExample/components/KeypadInputs.tsx b/03_source/mobile/src/pages/Demo2FaExample/components/KeypadInputs.tsx new file mode 100644 index 0000000..139e0f9 --- /dev/null +++ b/03_source/mobile/src/pages/Demo2FaExample/components/KeypadInputs.tsx @@ -0,0 +1,50 @@ +import { IonRow } from "@ionic/react"; +import { useEffect, useRef } from "react"; +import KeypadInput from "./KeypadInput"; + +const KeypadInputs = (props: any): JSX.Element => { + const { values, activeIndex, incorrect, correct } = props; + const keypadRef = useRef(null); + + useEffect(() => { + if (incorrect && keypadRef.current) { + keypadRef.current.classList.add("incorrect"); + + setTimeout(() => { + if (keypadRef.current) { + keypadRef.current.classList.remove("incorrect"); + } + }, 1000); + } + }, [incorrect]); + + useEffect(() => { + if (correct) { + if (keypadRef.current) { + keypadRef.current.classList.add("correct"); + } + } + }, [correct]); + + return ( + + {values.map((value: string, index: number) => { + const isActive = parseInt(index.toString()) === parseInt(activeIndex); + const isFilled = value !== "" ? true : false; + + return ( + + ); + })} + + ); +}; + +export default KeypadInputs; diff --git a/03_source/mobile/src/pages/Demo2FaExample/index.tsx b/03_source/mobile/src/pages/Demo2FaExample/index.tsx index e567d3c..4048784 100644 --- a/03_source/mobile/src/pages/Demo2FaExample/index.tsx +++ b/03_source/mobile/src/pages/Demo2FaExample/index.tsx @@ -1,38 +1,19 @@ -import { IonIcon, IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react'; +import { IonRouterOutlet, IonTabs } from '@ionic/react'; -import { cloudOutline, searchOutline } from 'ionicons/icons'; import { Route, Redirect } from 'react-router'; -import Tab1 from './AppPages/Tab1'; -import Tab2 from './AppPages/Tab2'; - -import './style.scss'; +import Home from './pages/Home'; function Demo2FaExample() { return ( - + - - - - - + + - + - - {/* */} - - - - Dashboard - - - - Search - - ); } diff --git a/03_source/mobile/src/pages/Demo2FaExample/pages/Home.module.scss b/03_source/mobile/src/pages/Demo2FaExample/pages/Home.module.scss new file mode 100644 index 0000000..3d0a7fe --- /dev/null +++ b/03_source/mobile/src/pages/Demo2FaExample/pages/Home.module.scss @@ -0,0 +1,30 @@ +.logo { + height: 4rem; + width: auto; +} + +.incorrect { + color: rgb(218, 67, 67); +} + +.successContainer { +} + +.successText { + background-color: rgba(26, 251, 120, 0.2); + color: rgb(67, 218, 112); + padding: 1rem; +} + +.successContinue { + font-weight: 700; + display: flex; + flex-direction: row; + justify-content: center; + align-content: center; + align-items: center; +} + +.successContinue ion-icon { + margin-top: 0.2rem; +} diff --git a/03_source/mobile/src/pages/Demo2FaExample/pages/Home.tsx b/03_source/mobile/src/pages/Demo2FaExample/pages/Home.tsx new file mode 100644 index 0000000..1cd7885 --- /dev/null +++ b/03_source/mobile/src/pages/Demo2FaExample/pages/Home.tsx @@ -0,0 +1,135 @@ +import { + IonButton, + IonButtons, + IonCol, + IonContent, + IonGrid, + IonHeader, + IonIcon, + IonImg, + IonPage, + IonRow, + IonToolbar, + useIonRouter, +} from '@ionic/react'; +import { arrowForwardOutline, chevronBackOutline } from 'ionicons/icons'; +import styles from './Home.module.scss'; +import KeypadInputs from '../components/KeypadInputs'; +import Keypad from '../components/Keypad'; +import { JSX, useEffect, useRef, useState } from 'react'; + +const Home = (): JSX.Element => { + const correctCode = [5, 9, 2, 5]; + const [keypadValues, setKeypadValues] = useState(['', '', '', '']); + const [activeIndex, setActiveIndex] = useState(0); + const successRef = useRef(null); + + const [incorrect, setIncorrect] = useState(false); + const [correct, setCorrect] = useState(false); + + const tempValues: { [key: string]: any } = {}; + + const handleClick = (index: number, value: any) => { + const stringKey = index.toString(); + tempValues[stringKey] = value; + + setKeypadValues(value); + setActiveIndex((activeIndex) => activeIndex + 1); + }; + + const handleRemove = () => { + const tempValues = [...keypadValues]; + tempValues[activeIndex - 1] = ''; + + setKeypadValues(tempValues); + activeIndex > 0 && setActiveIndex((activeIndex) => activeIndex - 1); + setIncorrect(false); + setCorrect(false); + }; + + const router = useIonRouter(); + function handleBackClick() { + router.goBack(); + } + + useEffect(() => { + if (parseInt(activeIndex.toString()) === parseInt(keypadValues.length.toString())) { + var error = false; + + keypadValues.forEach((value, index) => { + if (parseInt(value) !== parseInt(correctCode[index].toString())) { + error = true; + return false; + } + }); + + if (error) { + setIncorrect(true); + } else { + setCorrect(true); + + setTimeout(() => { + if (successRef.current) { + successRef.current.classList.remove('hidden'); + successRef.current.classList.add('success'); + } + }, 900); + } + } + }, [activeIndex]); + + return ( + + + + + + handleBackClick()}> + + + + + + + + + +

Verification required

+

Enter your 4 digit verification code

+
+
+ + + +

+ Awesome! You may continue. +
+ + Continue   + + +

+
+
+ + + {incorrect &&

Wrong code entered

} + +
+
+
+ ); +}; + +export default Home; diff --git a/03_source/mobile/src/pages/Demo2FaExample/style.scss b/03_source/mobile/src/pages/Demo2FaExample/style.scss deleted file mode 100644 index 37c1e1a..0000000 --- a/03_source/mobile/src/pages/Demo2FaExample/style.scss +++ /dev/null @@ -1,103 +0,0 @@ -#about-page { - ion-toolbar { - position: absolute; - - top: 0; - left: 0; - right: 0; - - --background: transparent; - --color: white; - } - - ion-toolbar ion-back-button, - ion-toolbar ion-button, - ion-toolbar ion-menu-button { - --color: white; - } - - .about-header { - position: relative; - - width: 100%; - height: 30%; - } - - .about-header .about-image { - position: absolute; - - top: 0; - left: 0; - bottom: 0; - right: 0; - - background-position: center; - background-size: cover; - background-repeat: no-repeat; - - opacity: 0; - - transition: opacity 500ms ease-in-out; - } - - .about-header .madison { - background-image: url('/assets/WeatherDemo/img/about/madison.jpg'); - } - - .about-header .austin { - background-image: url('/assets/WeatherDemo/img/about/austin.jpg'); - } - - .about-header .chicago { - background-image: url('/assets/WeatherDemo/img/about/chicago.jpg'); - } - - .about-header .seattle { - background-image: url('/assets/WeatherDemo/img/about/seattle.jpg'); - } - - .about-info { - position: relative; - margin-top: -10px; - border-radius: 10px; - background: var(--ion-background-color, #fff); - z-index: 2; // display rounded border above header image - } - - .about-info h3 { - margin-top: 0; - } - - .about-info ion-list { - padding-top: 0; - } - - .about-info p { - line-height: 130%; - - color: var(--ion-color-dark); - } - - .about-info ion-icon { - margin-inline-end: 32px; - } - - /* - * iOS Only - */ - - .ios .about-info { - --ion-padding: 19px; - } - - .ios .about-info h3 { - font-weight: 700; - } -} - -#date-input-popover { - --offset-y: -var(--ion-safe-area-bottom); - - --max-width: 90%; - --width: 336px; -} diff --git a/03_source/mobile/src/pages/Demo2FaExample/theme/variables.scss b/03_source/mobile/src/pages/Demo2FaExample/theme/variables.scss new file mode 100644 index 0000000..dc5200e --- /dev/null +++ b/03_source/mobile/src/pages/Demo2FaExample/theme/variables.scss @@ -0,0 +1,249 @@ +.demo-2fa-example { + /* Ionic Variables and Theming. For more info, please see: +http://ionicframework.com/docs/theming/ */ + + * { + font-family: 'Lato', sans-serif; + } + + .hidden { + display: none; + transition: 0.2s linear; + } + + /** Ionic CSS Variables **/ + :root { + /** primary **/ + --ion-color-primary: #3880ff; + --ion-color-primary-rgb: 56, 128, 255; + --ion-color-primary-contrast: #ffffff; + --ion-color-primary-contrast-rgb: 255, 255, 255; + --ion-color-primary-shade: #3171e0; + --ion-color-primary-tint: #4c8dff; + + /** secondary **/ + --ion-color-secondary: #3dc2ff; + --ion-color-secondary-rgb: 61, 194, 255; + --ion-color-secondary-contrast: #ffffff; + --ion-color-secondary-contrast-rgb: 255, 255, 255; + --ion-color-secondary-shade: #36abe0; + --ion-color-secondary-tint: #50c8ff; + + /** tertiary **/ + --ion-color-tertiary: #5260ff; + --ion-color-tertiary-rgb: 82, 96, 255; + --ion-color-tertiary-contrast: #ffffff; + --ion-color-tertiary-contrast-rgb: 255, 255, 255; + --ion-color-tertiary-shade: #4854e0; + --ion-color-tertiary-tint: #6370ff; + + /** success **/ + --ion-color-success: #2dd36f; + --ion-color-success-rgb: 45, 211, 111; + --ion-color-success-contrast: #ffffff; + --ion-color-success-contrast-rgb: 255, 255, 255; + --ion-color-success-shade: #28ba62; + --ion-color-success-tint: #42d77d; + + /** warning **/ + --ion-color-warning: #ffc409; + --ion-color-warning-rgb: 255, 196, 9; + --ion-color-warning-contrast: #000000; + --ion-color-warning-contrast-rgb: 0, 0, 0; + --ion-color-warning-shade: #e0ac08; + --ion-color-warning-tint: #ffca22; + + /** danger **/ + --ion-color-danger: #eb445a; + --ion-color-danger-rgb: 235, 68, 90; + --ion-color-danger-contrast: #ffffff; + --ion-color-danger-contrast-rgb: 255, 255, 255; + --ion-color-danger-shade: #cf3c4f; + --ion-color-danger-tint: #ed576b; + + /** dark **/ + --ion-color-dark: #222428; + --ion-color-dark-rgb: 34, 36, 40; + --ion-color-dark-contrast: #ffffff; + --ion-color-dark-contrast-rgb: 255, 255, 255; + --ion-color-dark-shade: #1e2023; + --ion-color-dark-tint: #383a3e; + + /** medium **/ + --ion-color-medium: #92949c; + --ion-color-medium-rgb: 146, 148, 156; + --ion-color-medium-contrast: #ffffff; + --ion-color-medium-contrast-rgb: 255, 255, 255; + --ion-color-medium-shade: #808289; + --ion-color-medium-tint: #9d9fa6; + + /** light **/ + --ion-color-light: #f4f5f8; + --ion-color-light-rgb: 244, 245, 248; + --ion-color-light-contrast: #000000; + --ion-color-light-contrast-rgb: 0, 0, 0; + --ion-color-light-shade: #d7d8da; + --ion-color-light-tint: #f5f6f9; + } + + .incorrect { + -webkit-animation: incorrect-animation 0.9s both; + animation: incorrect-animation 0.9s both; + } + + @-webkit-keyframes incorrect-animation { + 0% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(0.95, 1.05, 1); + transform: scale3d(0.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, 0.95, 1); + transform: scale3d(1.05, 0.95, 1); + } + + 100% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + } + + @keyframes incorrect-animation { + 0% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 30% { + -webkit-transform: scale3d(1.25, 0.75, 1); + transform: scale3d(1.25, 0.75, 1); + } + + 40% { + -webkit-transform: scale3d(0.75, 1.25, 1); + transform: scale3d(0.75, 1.25, 1); + } + + 50% { + -webkit-transform: scale3d(1.15, 0.85, 1); + transform: scale3d(1.15, 0.85, 1); + } + + 65% { + -webkit-transform: scale3d(0.95, 1.05, 1); + transform: scale3d(0.95, 1.05, 1); + } + + 75% { + -webkit-transform: scale3d(1.05, 0.95, 1); + transform: scale3d(1.05, 0.95, 1); + } + + 100% { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + } + + .correct { + -webkit-animation: correct-animation 1s ease-in both; + animation: correct-animation 1s ease-in both; + } + + @-webkit-keyframes correct-animation { + 0% { + -webkit-transform: translateY(0) rotateX(0) scale(1); + transform: translateY(0) rotateX(0) scale(1); + -webkit-transform-origin: 50% 1400px; + transform-origin: 50% 1400px; + opacity: 1; + } + + 100% { + -webkit-transform: translateY(-600px) rotateX(-30deg) scale(0); + transform: translateY(-600px) rotateX(-30deg) scale(0); + -webkit-transform-origin: 50% 100%; + transform-origin: 50% 100%; + opacity: 1; + } + } + + @keyframes correct-animation { + 0% { + -webkit-transform: translateY(0) rotateX(0) scale(1); + transform: translateY(0) rotateX(0) scale(1); + -webkit-transform-origin: 50% 1400px; + transform-origin: 50% 1400px; + opacity: 1; + } + + 100% { + -webkit-transform: translateY(-600px) rotateX(-30deg) scale(0); + transform: translateY(-600px) rotateX(-30deg) scale(0); + -webkit-transform-origin: 50% 100%; + transform-origin: 50% 100%; + opacity: 1; + } + } + + .success { + -webkit-animation: success-animation 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; + animation: success-animation 0.7s cubic-bezier(0.25, 0.46, 0.45, 0.94) both; + } + + @-webkit-keyframes success-animation { + 0% { + -webkit-transform: translateY(-600px) rotateX(-30deg) scale(0); + transform: translateY(-600px) rotateX(-30deg) scale(0); + -webkit-transform-origin: 50% 100%; + transform-origin: 50% 100%; + opacity: 0; + } + + 100% { + -webkit-transform: translateY(0) rotateX(0) scale(1); + transform: translateY(0) rotateX(0) scale(1); + -webkit-transform-origin: 50% 1400px; + transform-origin: 50% 1400px; + opacity: 1; + } + } + + @keyframes success-animation { + 0% { + -webkit-transform: translateY(-600px) rotateX(-30deg) scale(0); + transform: translateY(-600px) rotateX(-30deg) scale(0); + -webkit-transform-origin: 50% 100%; + transform-origin: 50% 100%; + opacity: 0; + } + + 100% { + -webkit-transform: translateY(0) rotateX(0) scale(1); + transform: translateY(0) rotateX(0) scale(1); + -webkit-transform-origin: 50% 1400px; + transform-origin: 50% 1400px; + opacity: 1; + } + } +}