├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── screenshots ├── desktop-solution.webp ├── header.webp ├── mobile-solution.webp └── tablet-solution.webp ├── src ├── App.jsx ├── assets │ ├── bg-card-back.png │ ├── bg-card-front.png │ ├── bg-main-desktop.png │ ├── bg-main-mobile.png │ ├── card-logo.svg │ ├── favicon-32x32.png │ └── icon-complete.svg ├── components │ ├── CardCredit.jsx │ ├── CardForm.jsx │ └── CardThanks.jsx ├── main.jsx └── styles │ ├── App.css │ ├── CardForm.css │ ├── CardThanks.css │ └── index.css └── vite.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": ["plugin:react/recommended", "standard"], 7 | "overrides": [], 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module" 11 | }, 12 | "plugins": ["react"], 13 | "rules": { 14 | "indent": [1, "tab"], 15 | "no-tabs": 0, 16 | "jsx-quotes": [1, "prefer-single"], 17 | "react/react-in-jsx-scope": "off", 18 | "react/prop-types": "off", 19 | "no-unused-vars": 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | style-guide.md 27 | /design 28 | test.html -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Cosmo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | Logo 6 | 7 | 8 | # Frontend Mentor - Interactive card details form solution 9 | 10 | This is a solution to the [Interactive card details form challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/interactive-card-details-form-XpS8cKZDWw). Frontend Mentor challenges help you improve your coding skills by building realistic projects. 11 | 12 | [Solution][solution-url] . [Live Page][live-page] 13 | 14 |
15 | 16 |
17 | Table of contents 18 | 19 | - [Overview](#overview) 20 | - [The challenge](#the-challenge) 21 | - [Screenshots](#screenshots) 22 | - [Links](#links) 23 | - [My process](#my-process) 24 | - [Built with](#built-with) 25 | - [What I learned](#what-i-learned) 26 | - [Author](#author) 27 | 28 |
29 | 30 | ## Overview 31 | 32 | ### The challenge 33 | 34 | Users should be able to: 35 | 36 | - Fill in the form and see the card details update in real-time 37 | - Receive error messages when the form is submitted if: 38 | - Any input field is empty 39 | - The card number, expiry date, or CVC fields are in the wrong format 40 | - View the optimal layout depending on their device's screen size 41 | - See hover, active, and focus states for interactive elements on the page 42 | 43 | #### Expected behaviour 44 | 45 | - Update the details on the card as the user fills in the fields 46 | - Validate the form fields when the form is submitted 47 | - If there are no errors, display the completed state 48 | - Reset the form when the user clicks "Continue" on the completed state 49 | 50 | ### Screenshots 51 | 52 | 53 | 54 | 58 | 62 | 66 | 67 |
55 | Mobile solution 57 | 59 | Tablet solution 61 | 63 | Desktop solution 65 |
68 | 69 | ### Links 70 | 71 | - [Solution][solution-url] 72 | - [Live Page][live-page] 73 | 74 | ## My process 75 | 76 | ### Built with 77 | 78 | - Semantic HTML5 markup 79 | - CSS custom properties 80 | - Flexbox 81 | - Desktop-first workflow 82 | - [React](https://reactjs.org/) - JS library 83 | - [Vite](https://interactive-card-details-form.vercel.app/) 84 | 85 |

(back to top)

86 | 87 | ### What I learned 88 | 89 | This challenge was a great learning opportunity, it was also very useful to reinforce previous knowledge, and face problems that had not been presented to me before. 90 | 91 |

(back to top)

92 | 93 | ## Author 94 | 95 | - Instagram - [@cosmo_art0](https://www.instagram.com/cosmo_art0/) 96 | - Frontend Mentor - [@CosmoArt](https://www.frontendmentor.io/profile/cosmoart) 97 | - Twitter - [@CosmoArt0](https://twitter.com/cosmoart0) 98 | - My personal page - [https://cosmoart.vercel.app](https://cosmoart.vercel.app) 99 | 100 |

(back to top)

101 | 102 | [live-page]: https://interactive-card-details-form.vercel.app 103 | [solution-url]: https://www.frontendmentor.io/solutions/interactive-card-details-form-solution-tKY7SrfIs_ 104 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Frontend Mentor | Interactive card details form 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "interactive-card-details-form-main", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^18.0.15", 17 | "@types/react-dom": "^18.0.6", 18 | "@vitejs/plugin-react": "^2.0.0", 19 | "eslint": "^8.35.0", 20 | "eslint-config-standard": "^17.0.0", 21 | "eslint-plugin-import": "^2.27.5", 22 | "eslint-plugin-n": "^15.6.1", 23 | "eslint-plugin-promise": "^6.1.1", 24 | "eslint-plugin-react": "^7.32.2", 25 | "vite": "^3.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /screenshots/desktop-solution.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmoart/Interactive-card-details-form/0996a1a6f388e29389fb26302046936eddcff01e/screenshots/desktop-solution.webp -------------------------------------------------------------------------------- /screenshots/header.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmoart/Interactive-card-details-form/0996a1a6f388e29389fb26302046936eddcff01e/screenshots/header.webp -------------------------------------------------------------------------------- /screenshots/mobile-solution.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmoart/Interactive-card-details-form/0996a1a6f388e29389fb26302046936eddcff01e/screenshots/mobile-solution.webp -------------------------------------------------------------------------------- /screenshots/tablet-solution.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmoart/Interactive-card-details-form/0996a1a6f388e29389fb26302046936eddcff01e/screenshots/tablet-solution.webp -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import CardThanks from './components/CardThanks' 3 | import CardForm from './components/CardForm' 4 | import CardCredit from './components/CardCredit' 5 | import './styles/App.css' 6 | 7 | function App () { 8 | const [formData, setFormData] = useState({ name: null, number: null, mm: null, yy: null, cvc: null }) 9 | const [validate, setValidate] = useState(false) 10 | 11 | const animateSlider = (validate) => { 12 | const axis = window.matchMedia('(max-width: 750px)').matches ? 'Y' : 'X' 13 | document.querySelector('.cardOverflow > div').style.transform = `translate${axis}(50${axis === 'Y' ? 'vh' : 'vw'})` 14 | 15 | document.body.classList.add('body-slider') 16 | 17 | setTimeout(() => { 18 | setValidate(validate) 19 | document.body.classList.remove('body-slider') 20 | document.querySelector('.cardOverflow > div').style.transform = 'translate(0)' 21 | }, 500) 22 | } 23 | 24 | return ( 25 | <> 26 | 27 |
28 |
29 | {validate 30 | ? 31 | : 32 | } 33 |
34 |
35 | 39 | 40 | ) 41 | } 42 | export default App 43 | -------------------------------------------------------------------------------- /src/assets/bg-card-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmoart/Interactive-card-details-form/0996a1a6f388e29389fb26302046936eddcff01e/src/assets/bg-card-back.png -------------------------------------------------------------------------------- /src/assets/bg-card-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmoart/Interactive-card-details-form/0996a1a6f388e29389fb26302046936eddcff01e/src/assets/bg-card-front.png -------------------------------------------------------------------------------- /src/assets/bg-main-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmoart/Interactive-card-details-form/0996a1a6f388e29389fb26302046936eddcff01e/src/assets/bg-main-desktop.png -------------------------------------------------------------------------------- /src/assets/bg-main-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmoart/Interactive-card-details-form/0996a1a6f388e29389fb26302046936eddcff01e/src/assets/bg-main-mobile.png -------------------------------------------------------------------------------- /src/assets/card-logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cosmoart/Interactive-card-details-form/0996a1a6f388e29389fb26302046936eddcff01e/src/assets/favicon-32x32.png -------------------------------------------------------------------------------- /src/assets/icon-complete.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/CardCredit.jsx: -------------------------------------------------------------------------------- 1 | export default function CreditCard({ formData }) { 2 | return ( 3 | 14 | ) 15 | } -------------------------------------------------------------------------------- /src/components/CardForm.jsx: -------------------------------------------------------------------------------- 1 | import '../styles/CardForm.css' 2 | 3 | export default function CardForm ({ setFormData, formData, animateSlider }) { 4 | const handleInput = (e) => { 5 | const { name, value } = e.target 6 | if (name === 'number') e.target.value = value.replace(/\s/g, '').replace(/(.{4})/g, '$1 ').trim().slice(0, 19) 7 | if (name === 'mm' || name === 'yy') e.target.value = value.toString().replace(/[^0-9]/g, '').substring(0, 2) 8 | if (name === 'mm' && value > 12) e.target.value = '12' 9 | if (name === 'cvc') e.target.value = value.substring(0, 4) 10 | 11 | setFormData({ ...formData, [name]: e.target.value }) 12 | } 13 | 14 | const handleError = (target, message = 'Error', type = 'add') => { 15 | if (type === 'add') { 16 | const submitBtn = document.querySelector('.btn-submit') 17 | submitBtn.classList.add('shake') 18 | submitBtn.addEventListener('animationend', () => submitBtn.classList.remove('shake')) 19 | } 20 | 21 | document.querySelector(`.label${target}`).nextElementSibling.innerHTML = message 22 | document.querySelector(`.label${target}`).nextElementSibling.classList[type === 'add' ? 'remove' : 'add']('info--hidden') 23 | document.querySelector(`[name="${target}"]`).classList[type]('input--error') 24 | } 25 | 26 | const handleSubmit = (e) => { 27 | e.preventDefault() 28 | 29 | for (const i in formData) { 30 | if (!formData[i]) { 31 | handleError(i, 'Can`t be blank') 32 | } else handleError(i, '', 'remove') 33 | } 34 | 35 | if (formData.number) { 36 | if (formData.number.length < 19) { 37 | handleError('number', 'Number is too short') 38 | } else if (formData.number.match(/[^0-9\s]/g)) { 39 | handleError('number', 'Wrong format, numbers only') 40 | } else handleError('number', '', 'remove') 41 | } 42 | 43 | if (formData.cvc) { 44 | if (formData.cvc.length < 3) { 45 | handleError('cvc', 'CVC is too short') 46 | } else handleError('cvc', '', 'remove') 47 | } 48 | 49 | if (!formData.mm) handleError('mm', 'Can`t be blank') 50 | if (!formData.yy) handleError('yy', 'Can`t be blank') 51 | 52 | if (document.querySelectorAll('.input--error').length === 0) animateSlider(true) 53 | } 54 | 55 | return ( 56 |
57 | 61 |

62 | 63 | 67 |

68 | 69 |
70 | 77 |

78 | 79 | 83 |

84 |
85 | 86 | 87 |
88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /src/components/CardThanks.jsx: -------------------------------------------------------------------------------- 1 | import '../styles/CardThanks.css' 2 | import iconComplete from '../assets/icon-complete.svg' 3 | 4 | export default function CardThanks ({ setFormData, animateSlider }) { 5 | const resetForm = () => { 6 | setFormData({ name: null, number: null, mm: null, yy: null, cvc: null }) 7 | animateSlider(false) 8 | } 9 | 10 | return ( 11 |
12 | 13 |

Thank you!

14 |

We've added your card details

15 | 16 |
17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App' 4 | import './styles/index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /src/styles/App.css: -------------------------------------------------------------------------------- 1 | .attribution { 2 | position: fixed; 3 | bottom: 0; 4 | right: 0; 5 | font-size: 12px; 6 | opacity: 0.5; 7 | } 8 | .attribution p { 9 | margin: 10px; 10 | } 11 | .cardFront, 12 | .cardBack { 13 | max-width: 28rem; 14 | height: 15.5rem; 15 | border-radius: 10px; 16 | background-size: cover; 17 | color: var(--White); 18 | position: relative; 19 | padding: 32px; 20 | } 21 | 22 | /* Card Front ========*/ 23 | .cardFront { 24 | background-image: url('../assets/bg-card-front.png'); 25 | background-size: cover; 26 | background-repeat: no-repeat; 27 | display: flex; 28 | flex-direction: column; 29 | justify-content: flex-end; 30 | margin: 0 auto 32px auto; 31 | } 32 | 33 | .cardFront div { 34 | display: flex; 35 | justify-content: space-between; 36 | width: 100%; 37 | word-spacing: 6px; 38 | } 39 | 40 | .cardFront > span { 41 | font-size: clamp(22px, 2.5vw, 27px); 42 | letter-spacing: 4px; 43 | margin-bottom: 25px; 44 | } 45 | 46 | .cardFront div span { 47 | font-size: 16px; 48 | } 49 | 50 | .cardFront div span:first-child { 51 | text-transform: uppercase; 52 | } 53 | 54 | /* Card Back =======*/ 55 | .cardBack { 56 | background-image: url('../assets/bg-card-back.png'); 57 | background-size: contain; 58 | background-size: 100% 100%; 59 | background-repeat: no-repeat; 60 | margin: 0 2rem 0 auto; 61 | } 62 | 63 | .cardBack span { 64 | position: absolute; 65 | right: 60px; 66 | top: 109px; 67 | font-size: 24px; 68 | } 69 | 70 | .cardFront::after { 71 | content: url('../assets/card-logo.svg'); 72 | position: absolute; 73 | top: 27px; 74 | left: 27px; 75 | } 76 | 77 | /* ===== */ 78 | .cardDeco { 79 | width: 54%; 80 | padding: 2rem; 81 | } 82 | 83 | .cardOverflow { 84 | width: 46%; 85 | overflow: hidden; 86 | } 87 | 88 | .cardOverflow > div { 89 | transition: transform 0.35s ease-in-out; 90 | } 91 | 92 | .btn-primary { 93 | background: var(--Verdarviolet); 94 | border: none; 95 | padding: 15px 20px; 96 | color: var(--White); 97 | cursor: pointer; 98 | border-radius: 6px; 99 | font-size: 18px; 100 | } 101 | 102 | .btn-primary:hover { 103 | opacity: 0.9; 104 | } 105 | 106 | .card-input { 107 | padding: 13px; 108 | border: 1px solid var(--Dargrayisviolet); 109 | border-radius: 7px; 110 | } 111 | 112 | .card-input::placeholder { 113 | opacity: 0.7; 114 | } 115 | 116 | @media screen and (max-width: 585px) { 117 | .cardFront { 118 | left: 0 !important; 119 | } 120 | 121 | .cardBack { 122 | right: 0 !important; 123 | } 124 | } 125 | 126 | @media screen and (max-width: 750px) { 127 | .cardFront, 128 | .cardBack { 129 | transform: scale(0.76) !important; 130 | } 131 | 132 | .cardDeco { 133 | width: 100%; 134 | } 135 | 136 | .cardFront, 137 | .cardBack { 138 | margin: 1rem auto; 139 | } 140 | 141 | .cardFront { 142 | margin: 1rem 2rem 1rem auto; 143 | position: absolute; 144 | width: inherit; 145 | z-index: 10; 146 | top: 8rem !important; 147 | } 148 | 149 | .cardFront { 150 | left: 3rem; 151 | } 152 | 153 | .cardBack { 154 | position: absolute; 155 | top: 2rem; 156 | right: 3rem; 157 | width: inherit; 158 | } 159 | 160 | .cardForm, 161 | .cardThanks { 162 | width: 100%; 163 | } 164 | 165 | .cardOverflow { 166 | width: 100%; 167 | } 168 | } 169 | 170 | @media screen and (max-width: 1400px) { 171 | .cardFront, 172 | .cardBack { 173 | transform: scale(0.85); 174 | } 175 | } 176 | 177 | @media screen and (max-width: 1010px) { 178 | .cardBack { 179 | margin: 0 auto; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/styles/CardForm.css: -------------------------------------------------------------------------------- 1 | .info--hidden { 2 | height: 0 !important; 3 | } 4 | 5 | .input--error { 6 | border: 1px solid var(--Red) !important; 7 | } 8 | 9 | .shake { 10 | animation: shake 0.7s cubic-bezier(0.36, 0.07, 0.19, 0.97) both; 11 | } 12 | 13 | @keyframes shake { 14 | 10%, 15 | 90% { 16 | transform: translate3d(-1px, 0, 0); 17 | background: var(--Red); 18 | } 19 | 20 | 20%, 21 | 80% { 22 | transform: translate3d(2px, 0, 0); 23 | } 24 | 25 | 30%, 26 | 50%, 27 | 70% { 28 | transform: translate3d(-4px, 0, 0); 29 | } 30 | 31 | 40%, 32 | 60% { 33 | transform: translate3d(4px, 0, 0); 34 | } 35 | } 36 | 37 | .cardForm { 38 | display: flex; 39 | flex-direction: column; 40 | max-width: 24rem; 41 | margin-left: 10%; 42 | padding: 1rem; 43 | } 44 | 45 | .cardForm label { 46 | margin-bottom: 16px; 47 | gap: 14px; 48 | text-transform: uppercase; 49 | display: flex; 50 | flex-direction: column; 51 | color: var(--Verdarviolet); 52 | font-size: 15px; 53 | } 54 | 55 | .cardForm input:hover { 56 | border: 1px solid hsl(249, 99%, 64%); 57 | } 58 | 59 | .labelmm { 60 | width: 50%; 61 | } 62 | 63 | .labelcvc { 64 | width: 50%; 65 | } 66 | 67 | .labelmm div { 68 | display: flex; 69 | gap: 10px; 70 | } 71 | 72 | .labelmm input { 73 | width: 100%; 74 | } 75 | 76 | .cvc-mmyy { 77 | display: flex; 78 | margin-bottom: 26px; 79 | position: relative; 80 | gap: 10px; 81 | } 82 | 83 | .cvc-mmyy .info { 84 | position: absolute; 85 | bottom: -40px; 86 | transform: translateY(-25px); 87 | } 88 | 89 | .cvc-mmyy .info:last-child { 90 | right: 1rem; 91 | } 92 | 93 | .info { 94 | transition: height 0.3s ease-in-out; 95 | font-size: 13px; 96 | margin-top: 4px; 97 | margin-bottom: 15px; 98 | overflow: hidden; 99 | height: 20px; 100 | color: var(--Red); 101 | transform: translateY(-10px); 102 | margin: 0; 103 | } 104 | 105 | @media screen and (max-width: 585px) { 106 | .App::before { 107 | height: 20rem !important; 108 | } 109 | .cardForm { 110 | margin-top: 19rem !important; 111 | } 112 | } 113 | 114 | @media screen and (max-width: 750px) { 115 | .cardForm { 116 | padding: 2rem; 117 | margin-left: auto; 118 | margin-top: 20rem; 119 | max-width: 100%; 120 | } 121 | 122 | .App::before { 123 | content: ''; 124 | height: 22rem; 125 | } 126 | } 127 | 128 | @media screen and (min-width: 1350px) { 129 | .cardForm { 130 | margin-left: 3.5rem; 131 | padding: 0; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/styles/CardThanks.css: -------------------------------------------------------------------------------- 1 | .cardThanks { 2 | text-align: center; 3 | color: var(--Verdarviolet); 4 | max-width: 30rem; 5 | margin-right: auto; 6 | } 7 | 8 | .cardThanks p:first-of-type { 9 | font-size: 28px; 10 | text-transform: uppercase; 11 | margin-bottom: 0; 12 | } 13 | 14 | .cardThanks p:last-of-type { 15 | opacity: 0.8; 16 | font-size: 16px; 17 | margin-top: 10px; 18 | margin-bottom: 30px; 19 | } 20 | 21 | .cardThanks .btn-primary { 22 | width: 60%; 23 | } 24 | 25 | @media screen and (max-width: 750px) { 26 | .cardThanks { 27 | margin: auto; 28 | margin-top: 19rem; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/styles/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --Lineargradient: hsl(249, 99%, 64%) to hsl(278, 94%, 30%); 3 | --Red: hsl(0, 100%, 66%); 4 | 5 | --White: hsl(0, 0%, 100%); 6 | --Lighgrayishviolet: hsl(270, 3%, 87%); 7 | --Dargrayisviolet: hsl(279, 6%, 55%); 8 | --Verdarviolet: hsl(278, 68%, 11%); 9 | } 10 | 11 | body { 12 | font-family: 'Space Grotesk', sans-serif; 13 | background: var(--White); 14 | } 15 | 16 | body::after { 17 | content: ''; 18 | background-image: url('../assets/bg-main-desktop.png'); 19 | background-size: cover; 20 | width: 33.5%; 21 | height: 100vh; 22 | position: absolute; 23 | top: 0; 24 | left: 0; 25 | z-index: -100; 26 | transition: width 0.35s ease-in-out, height 0.35s ease-in-out; 27 | } 28 | 29 | .body-slider::after { 30 | width: 100%; 31 | } 32 | 33 | #root { 34 | display: flex; 35 | min-height: 100vh; 36 | align-items: center; 37 | justify-content: center; 38 | } 39 | 40 | @media screen and (max-width: 750px) { 41 | #root { 42 | flex-direction: column; 43 | } 44 | 45 | body::after { 46 | background-image: url('../assets/bg-main-mobile.png'); 47 | height: 40%; 48 | width: 100%; 49 | } 50 | 51 | .body-slider::after { 52 | height: 100%; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()] 7 | }) 8 | --------------------------------------------------------------------------------