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;
+ }
+ }
+}