├── .gitignore ├── LICENSE ├── README.md ├── _archive └── screenshots │ ├── project-screens.png │ ├── screenshot-1.png │ ├── screenshot-2.png │ ├── screenshot-3.png │ └── screenshot-4.png ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── assets │ ├── images │ │ ├── apple.svg │ │ ├── bag.svg │ │ ├── black.jpg │ │ ├── blue.jpg │ │ ├── chip.jpeg │ │ ├── explore1.jpg │ │ ├── explore2.jpg │ │ ├── favicon.ico │ │ ├── frame.png │ │ ├── hero.jpeg │ │ ├── pause.svg │ │ ├── play.svg │ │ ├── replay.svg │ │ ├── right.svg │ │ ├── search.svg │ │ ├── watch.svg │ │ ├── white.jpg │ │ └── yellow.jpg │ ├── react.svg │ └── videos │ │ ├── explore.mp4 │ │ ├── frame.mp4 │ │ ├── hero.mp4 │ │ ├── highlight-first.mp4 │ │ ├── hightlight-fourth.mp4 │ │ ├── hightlight-sec.mp4 │ │ ├── hightlight-third.mp4 │ │ └── smallHero.mp4 ├── models │ └── scene.glb └── vite.svg ├── src ├── App.jsx ├── components │ ├── Features.jsx │ ├── Footer.jsx │ ├── Hero.jsx │ ├── Highlights.jsx │ ├── HowItWorks.jsx │ ├── Navbar.jsx │ ├── VideoCarousel.jsx │ ├── iphonemodel │ │ ├── IPhone.jsx │ │ ├── Lights.jsx │ │ ├── Model.jsx │ │ └── ModelView.jsx │ └── loader │ │ ├── AppLoader.jsx │ │ ├── Loader.css │ │ └── Loader.jsx ├── constants │ └── index.js ├── index.css ├── main.jsx └── utils │ ├── animations.js │ └── index.js ├── tailwind.config.js └── vite.config.js /.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? -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Apple iPhone 15 Website Clone
♨ [ ʀᴇᴀᴄᴛ ᴘʀᴏᴊᴇᴄᴛ ] ♨

2 | 3 | ## ɪ ⁃ ᴘʀᴏᴊᴇᴄᴛ ɪɴꜰᴏ 4 | 5 | This repository is a **frontend-only** clone of Apple’s iPhone 15 Pro website, built with **React.js** and **TailwindCSS**, and powered by **Vite** for a fast and modern development experience. 6 | 7 | > [!Note] 8 | > This project aims to replicate the look and feel of the original website, showcasing the design and animations without functional backend elements. 9 | 10 |
11 | 12 |
13 | 14 | ⁃ ᴄᴏɴᴛᴇɴᴛꜱ ⁃ 15 | [ᴀᴘᴘ ꜱᴄʀᴇᴇɴꜱ](#ɪɪ--ᴠɪꜱᴜᴀʟ-ᴛᴏᴜʀ-ᴏꜰ-ᴛʜᴇ-ᴀᴘᴘ-ꜱᴄʀᴇᴇɴꜱ) 16 | | [ᴅᴇᴠ ꜰᴇᴀᴛᴜʀᴇꜱ](#ɪɪɪ--ʙᴇʜɪɴᴅ-ᴛʜᴇ-ᴄᴏᴅᴇ-ᴅᴇᴠ-ꜰᴇᴀᴛᴜʀᴇꜱ) 17 | | [ᴘʀᴏᴊᴇᴄᴛ ʙʀᴀɴᴄʜᴇꜱ](#ᴠ--ᴘʀᴏᴊᴇᴄᴛ-ʙʀᴀɴᴄʜᴇꜱ) 18 | | [ᴘʀᴏᴊᴇᴄᴛ ɴᴏᴛᴇꜱ](#ᴠɪ--ᴘʀᴏᴊᴇᴄᴛ-ɴᴏᴛᴇꜱ) 19 | 20 | 21 |
22 | react.js 23 | three.js 24 | greensock 25 | tailwindcss 26 |
27 | 28 |
29 | 30 |
31 | 32 | ## ɪɪ ⁃ ᴠɪꜱᴜᴀʟ ᴛᴏᴜʀ ᴏꜰ ᴛʜᴇ ᴀᴘᴘ: ꜱᴄʀᴇᴇɴꜱ 33 | 34 |

35 | 38 |

39 | 40 | 41 | ## ɪɪɪ ⁃ ʙᴇʜɪɴᴅ ᴛʜᴇ ᴄᴏᴅᴇ: ᴅᴇᴠ ꜰᴇᴀᴛᴜʀᴇꜱ 42 | 43 | ### ⭓ Features 44 | 45 | 👉 **Beautiful Smooth Animations with GSAP**: Elevate the user experience with seamless and captivating animations. 46 | 47 | 👉 **3D Model Rendering in Various Colors and Sizes**: Interact with dynamic 3D models of the iPhone 15 Pro, displayed in multiple colors and sizes using Three.js. 48 | 49 | 👉 **Custom Video Carousel (GSAP)**: A unique and interactive video carousel designed to engage users and enhance browsing. 50 | 51 | 👉 **Completely Responsive Design**: Optimized for every screen size, delivering a consistent experience across all devices. 52 | 53 | 👉 **Showcases UI/UX Excellence**: Focused entirely on frontend design and user interactions, with no backend or functional features implemented. 54 | 55 | 👉 **Reusable and Clean Code Architecture**: Built with reusability and maintainability in mind for seamless project scaling. 56 | 57 | 58 |
59 | 60 | ### ⭓ Technologies Used 61 | - **React.js**: Component-based structure for building the UI. 62 | - **Three.js**: Rendering 3D models and effects. 63 | - **React Three Fiber**: Simplified Three.js integration in React. 64 | - **React Three Drei**: Helper components for easier 3D development. 65 | - **GSAP (GreenSock)**: Creating fluid and engaging animations. 66 | - **Vite**: Modern development environment for blazing-fast builds. 67 | - **Tailwind CSS**: For fast, responsive, and modern styling. 68 | 69 |
70 | 71 | 72 | ## 🚀 Quick Start 73 | 74 | Follow these steps to set up the project on your local machine. 75 | 76 | ### Prerequisites 77 | 78 | Ensure you have the following installed: 79 | - [Git](https://git-scm.com/) 80 | - [Node.js](https://nodejs.org/en) 81 | - [npm](https://www.npmjs.com/) (Node Package Manager) 82 | 83 | ### Clone the Repository 84 | 85 | ```bash 86 | git clone https://github.com/NovaLogics/apple-react-web-app.git 87 | cd apple-react-web-app 88 | ``` 89 | 90 | ### Install Dependencies 91 | Use npm to install the required dependencies: 92 | 93 | ```bash 94 | npm install 95 | ``` 96 | 97 | ### Run the Project 98 | Start the development server: 99 | ```bash 100 | npm run dev 101 | ``` 102 | Then, open `http://localhost:5173` in your browser to see the project in action. 103 | 104 |
105 |
106 | 107 | 108 | > [!Note] 109 | > Build for Production 110 | > If you need a production-ready build, run:
111 | > > npm run build 112 | 113 |
114 | 115 | ## ᴠ ⁃ ᴘʀᴏᴊᴇᴄᴛ ʙʀᴀɴᴄʜᴇꜱ 116 | 117 | 118 | 119 | > **PRODUCTION BRANCH :** 120 | > Stable code for deployment 121 | > ➲ [main][branch-main] 122 | 123 | 124 | > **DEVELOPMENT BRANCH :** 125 | > Active codebase for ongoing development efforts (New features, bug fixes, and improvements..) 126 | > ➲ [dev-current][branch-development] 127 | 128 |
129 | 130 | #### ➲ Code from the scratch 131 | Explore the branches: Stage 1–9 132 | 133 | ##### Branches 134 | 135 | 1. [**dev-stage-1-init_project**](https://github.com/NovaLogics/apple-react-web-app/tree/dev-stage-1-init_project) 136 | Initial project setup. 137 | 138 | 2. [**dev-stage-2-add_resource**](https://github.com/NovaLogics/apple-react-web-app/tree/dev-stage-2-add_resource) 139 | Added required resources. 140 | 141 | 3. [**dev-stage-3-init_development**](https://github.com/NovaLogics/apple-react-web-app/tree/dev-stage-3-init_development) 142 | Initialized development environment. 143 | 144 | 4. [**dev-stage-4-impl_components**](https://github.com/NovaLogics/apple-react-web-app/tree/dev-stage-4-impl_components) 145 | Implemented core UI components. 146 | 147 | 5. [**dev-stage-5-impl_video_carousel**](https://github.com/NovaLogics/apple-react-web-app/tree/dev-stage-5-impl_video_carousel) 148 | Developed the video carousel feature. 149 | 150 | 6. [**dev-stage-6-impl_3d_model**](https://github.com/NovaLogics/apple-react-web-app/tree/dev-stage-6-impl_3d_model) 151 | Added 3D model rendering functionality. 152 | 153 | 7. [**dev-stage-7-impl_device_features**](https://github.com/NovaLogics/apple-react-web-app/tree/dev-stage-7-impl_device_features) 154 | Implemented the "Device Features" section. 155 | 156 | 8. [**dev-stage-8-impl_feat_how_it_works**](https://github.com/NovaLogics/apple-react-web-app/tree/dev-stage-8-impl_feat_how_it_works) 157 | Added the "How It Works" section. 158 | 159 | 9. [**dev-stage-9-impl_section_footer**](https://github.com/NovaLogics/apple-react-web-app/tree/dev-stage-9-impl_section_footer) 160 | Implemented the footer section. 161 | 162 | 163 | [branch-main]: https://github.com/NovaLogics/apple-react-web-app/tree/main 164 | 165 | [branch-development]: https://github.com/NovaLogics/apple-react-web-app/tree/dev-current 166 | 167 |
168 | 169 | ## ᴠɪ ⁃ ᴘʀᴏᴊᴇᴄᴛ ɴᴏᴛᴇꜱ 170 | 171 | N/A 172 | -------------------------------------------------------------------------------- /_archive/screenshots/project-screens.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/_archive/screenshots/project-screens.png -------------------------------------------------------------------------------- /_archive/screenshots/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/_archive/screenshots/screenshot-1.png -------------------------------------------------------------------------------- /_archive/screenshots/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/_archive/screenshots/screenshot-2.png -------------------------------------------------------------------------------- /_archive/screenshots/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/_archive/screenshots/screenshot-3.png -------------------------------------------------------------------------------- /_archive/screenshots/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/_archive/screenshots/screenshot-4.png -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactThree from 'eslint-plugin-react-three' 5 | import reactHooks from 'eslint-plugin-react-hooks' 6 | import reactRefresh from 'eslint-plugin-react-refresh' 7 | 8 | export default [ 9 | { ignores: ['dist'] }, 10 | { 11 | files: ['**/*.{js,jsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | parserOptions: { 16 | ecmaVersion: 'latest', 17 | ecmaFeatures: { jsx: true }, 18 | sourceType: 'module', 19 | }, 20 | }, 21 | settings: { react: { version: '18.3' } }, 22 | plugins: { 23 | react, 24 | 'react-hooks': reactHooks, 25 | 'react-refresh': reactRefresh, 26 | '@react-three':reactThree, 27 | }, 28 | rules: { 29 | ...js.configs.recommended.rules, 30 | ...react.configs.recommended.rules, 31 | ...react.configs['jsx-runtime'].rules, 32 | ...reactHooks.configs.recommended.rules, 33 | 'react/jsx-no-target-blank': 'off', 34 | 'react-refresh/only-export-components': [ 35 | 'warn', 36 | { allowConstantExport: true }, 37 | ], 38 | }, 39 | }, 40 | ] 41 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | iPhone 15 Pro - Apple 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apple-react-web-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@gsap/react": "^2.1.1", 14 | "@react-three/drei": "^9.120.6", 15 | "@react-three/fiber": "^8.17.11", 16 | "gsap": "^3.12.5", 17 | "react": "^18.3.1", 18 | "react-dom": "^18.3.1", 19 | "three": "^0.172.0" 20 | }, 21 | "devDependencies": { 22 | "@eslint/js": "^9.17.0", 23 | "@types/react": "^18.3.18", 24 | "@types/react-dom": "^18.3.5", 25 | "@vitejs/plugin-react": "^4.3.4", 26 | "autoprefixer": "^10.4.20", 27 | "eslint": "^9.17.0", 28 | "eslint-plugin-react": "^7.37.2", 29 | "eslint-plugin-react-hooks": "^5.0.0", 30 | "eslint-plugin-react-refresh": "^0.4.16", 31 | "globals": "^15.14.0", 32 | "postcss": "^8.4.49", 33 | "tailwindcss": "^3.4.17", 34 | "vite": "^6.0.5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/assets/images/apple.svg: -------------------------------------------------------------------------------- 1 | 8 | 13 | -------------------------------------------------------------------------------- /public/assets/images/bag.svg: -------------------------------------------------------------------------------- 1 | 9 | 14 | -------------------------------------------------------------------------------- /public/assets/images/black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/images/black.jpg -------------------------------------------------------------------------------- /public/assets/images/blue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/images/blue.jpg -------------------------------------------------------------------------------- /public/assets/images/chip.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/images/chip.jpeg -------------------------------------------------------------------------------- /public/assets/images/explore1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/images/explore1.jpg -------------------------------------------------------------------------------- /public/assets/images/explore2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/images/explore2.jpg -------------------------------------------------------------------------------- /public/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/images/favicon.ico -------------------------------------------------------------------------------- /public/assets/images/frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/images/frame.png -------------------------------------------------------------------------------- /public/assets/images/hero.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/images/hero.jpeg -------------------------------------------------------------------------------- /public/assets/images/pause.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/images/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/images/replay.svg: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/assets/images/right.svg: -------------------------------------------------------------------------------- 1 | 9 | 13 | -------------------------------------------------------------------------------- /public/assets/images/search.svg: -------------------------------------------------------------------------------- 1 | 9 | 14 | -------------------------------------------------------------------------------- /public/assets/images/watch.svg: -------------------------------------------------------------------------------- 1 | 9 | 13 | -------------------------------------------------------------------------------- /public/assets/images/white.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/images/white.jpg -------------------------------------------------------------------------------- /public/assets/images/yellow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/images/yellow.jpg -------------------------------------------------------------------------------- /public/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/videos/explore.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/videos/explore.mp4 -------------------------------------------------------------------------------- /public/assets/videos/frame.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/videos/frame.mp4 -------------------------------------------------------------------------------- /public/assets/videos/hero.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/videos/hero.mp4 -------------------------------------------------------------------------------- /public/assets/videos/highlight-first.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/videos/highlight-first.mp4 -------------------------------------------------------------------------------- /public/assets/videos/hightlight-fourth.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/videos/hightlight-fourth.mp4 -------------------------------------------------------------------------------- /public/assets/videos/hightlight-sec.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/videos/hightlight-sec.mp4 -------------------------------------------------------------------------------- /public/assets/videos/hightlight-third.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/videos/hightlight-third.mp4 -------------------------------------------------------------------------------- /public/assets/videos/smallHero.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/assets/videos/smallHero.mp4 -------------------------------------------------------------------------------- /public/models/scene.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NovaLogics/apple-react-web-app/bddde49a6780b04c915b63a131f25160302287c3/public/models/scene.glb -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import Navbar from "./components/Navbar" 2 | import Hero from "./components/Hero" 3 | import Highlights from "./components/Highlights" 4 | import Model from "./components/iphonemodel/Model" 5 | import Features from "./components/Features" 6 | import HowItWorks from "./components/HowItWorks" 7 | import Footer from "./components/Footer" 8 | import AppLoader from "./components/loader/AppLoader" 9 | 10 | import { useState, useEffect } from "react"; 11 | 12 | 13 | const App = () => { 14 | const [isLoading, setIsLoading] = useState(true); 15 | const [fadeOut, setFadeOut] = useState(false); 16 | 17 | useEffect(() => { 18 | const timer = setTimeout(() => { 19 | setFadeOut(true); 20 | setTimeout(() => { 21 | setIsLoading(false); 22 | }, 1000); 23 | }); 24 | return () => clearTimeout(timer); 25 | }, []); 26 | 27 | return ( 28 |
29 | {isLoading ? ( 30 | 31 | ) : ( 32 | <> 33 | 34 | 35 | 36 | 37 | 38 | 39 |
43 | ); 44 | }; 45 | 46 | export default App; 47 | -------------------------------------------------------------------------------- /src/components/Features.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { useGSAP } from '@gsap/react'; 3 | import gsap from 'gsap'; 4 | import { animateWithGsap } from '../utils/animations'; 5 | import { explore1Img, explore2Img, exploreVideo } from '../utils'; 6 | 7 | const ANIMATIONS = { 8 | VIDEO_SCROLL_TRIGGER: { 9 | trigger: "#exploreVideo", 10 | toggleActions: 'restart reverse restart reverse', 11 | start: '-10% bottom', 12 | }, 13 | TITLE_ANIMATION: { y: 0, opacity: 1 }, 14 | GROW_ANIMATION: { scale: 1, opacity: 1, ease: 'power1' }, 15 | TEXT_ANIMATION: { y: 0, opacity: 1, ease: 'power2.inOut', duration: 1 }, 16 | }; 17 | 18 | const DURATION = { 19 | SCRUB: 5.5, 20 | }; 21 | 22 | const Features = () => { 23 | const videoRef = useRef(); 24 | 25 | // Init animations using GSAP 26 | useGSAP(() => { 27 | gsap.to("#exploreVideo", { 28 | scrollTrigger: ANIMATIONS.VIDEO_SCROLL_TRIGGER, 29 | onComplete: () => { 30 | videoRef.current.play(); 31 | }, 32 | }); 33 | 34 | animateWithGsap("#features_title", ANIMATIONS.TITLE_ANIMATION); 35 | animateWithGsap(".g_grow", ANIMATIONS.GROW_ANIMATION, { scrub: DURATION.SCRUB }); 36 | animateWithGsap(".g_text", ANIMATIONS.TEXT_ANIMATION); 37 | }, []); 38 | 39 | return ( 40 |
41 |
42 |
43 |

44 | Explore the full story. 45 |

46 |
47 | 48 |
49 |
50 |

iPhone.

51 |

Forged in Titanium.

52 |
53 | 54 |
55 |
56 | 67 |
68 | 69 |
70 |
71 |
72 | Titanium 73 |
74 |
75 | Titanium 2 76 |
77 |
78 | 79 |
80 |
81 |

82 | iPhone 15 Pro is{' '} 83 | 84 | the first iPhone to feature an aerospace-grade titanium design 85 | , 86 | using the same alloy that spacecrafts use for missions to Mars. 87 |

88 |
89 | 90 |
91 |

92 | Titanium has one of the best strength-to-weight ratios of any metal, making these our{' '} 93 | lightest Pro models ever. You'll notice the difference the moment you pick one up. 94 |

95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | ); 103 | }; 104 | 105 | export default Features; 106 | -------------------------------------------------------------------------------- /src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { footerLinks } from '../constants'; 3 | 4 | // Constants 5 | const PHONE_NUMBER = '000800-040-1966'; 6 | const COPYRIGHT_TEXT = 'Copyright @ 2024 Apple Inc. All rights reserved.'; 7 | const SHOP_MESSAGE = 'More ways to shop:'; 8 | const FIND_STORE_TEXT = 'Find an Apple Store'; 9 | const OTHER_RETAILER_TEXT = 'other retailer'; 10 | const SEPARATOR = ' | '; 11 | 12 | const Footer = () => { 13 | return ( 14 | 58 | ); 59 | }; 60 | 61 | export default Footer; 62 | -------------------------------------------------------------------------------- /src/components/Hero.jsx: -------------------------------------------------------------------------------- 1 | import gsap from "gsap"; 2 | import { useGSAP } from "@gsap/react"; 3 | import { heroVideo, smallHeroVideo } from "../utils"; 4 | import { useEffect, useState } from "react"; 5 | 6 | const MOBILE_BREAKPOINT = 760; 7 | const DEBOUNCE_DELAY = 100; 8 | const HERO_FADE_IN_DELAY = 2; 9 | const CALL_TO_ACTION_FADE_IN_DELAY = 2; 10 | const EVENT_RESIZE = "resize"; 11 | 12 | const Hero = () => { 13 | const [videoSrc, setVideoSrc] = useState( 14 | window.innerWidth < MOBILE_BREAKPOINT ? smallHeroVideo : heroVideo 15 | ); 16 | 17 | // Helper function to update video source based on screen size 18 | const updateVideoSrc = () => { 19 | const newSrc = window.innerWidth < MOBILE_BREAKPOINT ? smallHeroVideo : heroVideo; 20 | setVideoSrc(newSrc); 21 | }; 22 | 23 | // Debounced resize handler to prevent excessive calls 24 | useEffect(() => { 25 | const handleResize = () => { 26 | clearTimeout(window.resizeTimeout); 27 | window.resizeTimeout = setTimeout(updateVideoSrc, DEBOUNCE_DELAY); 28 | }; 29 | 30 | window.addEventListener(EVENT_RESIZE, handleResize); 31 | 32 | return () => { 33 | clearTimeout(window.resizeTimeout); 34 | window.removeEventListener(EVENT_RESIZE, handleResize); 35 | }; 36 | }, []); 37 | 38 | // GSAP animations 39 | useGSAP(() => { 40 | gsap.to("#hero", 41 | { opacity: 1, delay: HERO_FADE_IN_DELAY } 42 | ); 43 | gsap.to("#callToAction", 44 | { opacity: 1, y: -50, delay: CALL_TO_ACTION_FADE_IN_DELAY } 45 | ); 46 | }); 47 | 48 | return ( 49 |
50 | {/* Hero Content */} 51 |
52 |

53 | iPhone 15 Pro 54 |

55 |
56 | 65 |
66 |
67 | 68 | {/* Call to Action */} 69 |
73 | 74 | Buy 75 | 76 |

77 | From $999 or $199/month 78 |

79 |
80 |
81 | ); 82 | }; 83 | 84 | export default Hero; 85 | -------------------------------------------------------------------------------- /src/components/Highlights.jsx: -------------------------------------------------------------------------------- 1 | import { useGSAP } from "@gsap/react"; 2 | import gsap from "gsap"; 3 | import { rightImg, watchImg } from "../utils"; 4 | import VideoCarousel from "./VideoCarousel"; 5 | 6 | // Animation constants 7 | const ANIMATION = { 8 | TITLE: { opacity: 1, y: 0 }, 9 | LINK: { opacity: 1, y: 0, duration: 1, stagger: 0.25 }, 10 | }; 11 | 12 | const Highlights = () => { 13 | useGSAP(() => { 14 | gsap.to("#title", ANIMATION.TITLE); 15 | gsap.to(".highlight-link", ANIMATION.LINK); 16 | }, []); 17 | 18 | const HighlightLink = ({ text, imgSrc, altText, style }) => ( 19 | 20 | {text} 21 | {altText} 22 | 23 | ); 24 | 25 | return ( 26 |
30 |
31 |
32 | {/* Title */} 33 |

34 | Get the highlights. 35 |

36 | 37 | {/* Links */} 38 |
39 | 44 |

45 | | 46 |

47 | 53 |
54 |
55 | 56 | {/* Video Carousel */} 57 | 58 |
59 |
60 | ); 61 | }; 62 | 63 | export default Highlights; 64 | -------------------------------------------------------------------------------- /src/components/HowItWorks.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | import { chipImg, frameImg, frameVideo } from '../utils'; 3 | import { useGSAP } from '@gsap/react'; 4 | import gsap from 'gsap'; 5 | import { animateWithGsap } from '../utils/animations'; 6 | 7 | const SCROLL_TRIGGER_START = "-10% bottom"; 8 | const CHIP_OPACITY = 0; 9 | const CHIP_SCALE = 2; 10 | const CHIP_ANIMATION_DURATION = 1.5; 11 | const FADE_IN_DURATION = 1; 12 | const EASE_TYPE = "power2.inOut"; 13 | const VIDEO_TYPE = "video/mp4"; 14 | const CHIP_IMAGE_DIMENSIONS = 180; 15 | 16 | const HowItWorks = () => { 17 | 18 | const videoRef = useRef(); 19 | 20 | // GSAP animations 21 | useGSAP(() => { 22 | // Animation for chip 23 | gsap.from('#chip', { 24 | scrollTrigger: { 25 | trigger: "#chip", 26 | toggleActions: 'restart restart restart restart', 27 | start: SCROLL_TRIGGER_START, 28 | }, 29 | opacity: CHIP_OPACITY, 30 | scale: CHIP_SCALE, 31 | duration: CHIP_ANIMATION_DURATION, 32 | ease: EASE_TYPE, 33 | }); 34 | 35 | // Fade-in animation 36 | animateWithGsap('.g_fadeIn', { 37 | opacity: 1, 38 | y: 0, 39 | duration: FADE_IN_DURATION, 40 | ease: EASE_TYPE, 41 | }); 42 | }, []); 43 | 44 | return ( 45 |
46 |
47 | {/* Chip Section */} 48 |
49 | chip 55 |
56 | 57 | {/* Title and Subtitle */} 58 |
59 |

60 | A17 Pro chip. 61 |
A monster win for gaming. 62 |

63 | 64 |

65 | It's here. The biggest redesign in the history of Apple GPUs. 66 |

67 |
68 | 69 | {/* Video Frame Section */} 70 |
71 |
72 |
73 | frame 78 |
79 |
80 | 90 |
91 |
92 |

93 | Honkai: Star Rail 94 |

95 |
96 | 97 | {/* Text Content Section */} 98 |
99 |
100 |

101 | A17 Pro is an entirely new class of iPhone chip that delivers our{' '} 102 | 103 | best graphic performance by far 104 | . 105 |

106 | 107 |

108 | Mobile{' '} 109 | 110 | games will look and feel so immersive 111 | , 112 | with incredibly detailed environments and characters. 113 |

114 |
115 | 116 |
117 |

New

118 |

Pro-class GPU

119 |

with 6 cores

120 |
121 |
122 |
123 |
124 | ); 125 | }; 126 | 127 | export default HowItWorks; 128 | -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import { appleImg, bagImg, searchImg } from "../utils"; 2 | import { navLists } from "../constants"; 3 | 4 | const Navbar = () => { 5 | 6 | // Render navigation items 7 | const renderNavItems = () => 8 | navLists.map((nav) => ( 9 |
  • 12 | {nav} 13 |
  • 14 | )); 15 | 16 | return ( 17 |
    18 | 33 |
    34 | ); 35 | }; 36 | 37 | export default Navbar; 38 | -------------------------------------------------------------------------------- /src/components/VideoCarousel.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from "react"; 2 | import gsap from "gsap"; 3 | import { useGSAP } from "@gsap/react"; 4 | import { hightlightsSlides } from "../constants"; 5 | import { pauseImg, playImg, replayImg } from "../utils"; 6 | 7 | const SELECTORS = { 8 | slider: "#slider", 9 | video: "#video", 10 | title: "#title", 11 | }; 12 | 13 | const VIDEO_WIDTH = { 14 | SMALL_SCREEN: "10vw", 15 | LARGE_SCREEN: "4vw", 16 | DEFAULT: "12px", 17 | }; 18 | 19 | const BG_COLOR = { 20 | PLAYING: "white", 21 | DEFAULT: "#afafaf", 22 | }; 23 | 24 | const VideoCarousel = () => { 25 | const videoRef = useRef([]); 26 | const videoSpanRef = useRef([]); 27 | const videoDivRef = useRef([]); 28 | 29 | // State management for video controls and playback 30 | const [video, setVideo] = useState({ 31 | isEnd: false, 32 | startPlay: false, 33 | videoId: 0, 34 | isLastVideo: false, 35 | isPlaying: false, 36 | }); 37 | 38 | const [loadedData, setLoadedData] = useState([]); 39 | 40 | const { isEnd, isLastVideo, startPlay, videoId, isPlaying } = video; 41 | 42 | const handleVideoEnd = (index) => { 43 | setVideo((prevVideo) => ({ 44 | ...prevVideo, 45 | isEnd: true, 46 | videoId: index + 1, 47 | })); 48 | }; 49 | 50 | const handleVideoLast = () => { 51 | setVideo((prevVideo) => ({ 52 | ...prevVideo, 53 | isLastVideo: true, 54 | })); 55 | }; 56 | 57 | const handleVideoReset = () => { 58 | setVideo((prevVideo) => ({ 59 | ...prevVideo, 60 | isLastVideo: false, 61 | videoId: 0, 62 | })); 63 | }; 64 | 65 | // Toggle play/pause state 66 | const togglePlayPause = () => { 67 | setVideo((prevVideo) => ({ 68 | ...prevVideo, 69 | isPlaying: !prevVideo.isPlaying, 70 | })); 71 | }; 72 | 73 | const getControlButtonIcon = () => { 74 | if (isLastVideo) return replayImg; 75 | return isPlaying ? pauseImg : playImg; 76 | }; 77 | 78 | const getControlButtonAlt = () => { 79 | if (isLastVideo) return "replay"; 80 | return isPlaying ? "pause" : "play"; 81 | }; 82 | 83 | // Handle metadata loaded for each video 84 | const handleLoadedMetaData = (index, element) => { 85 | setLoadedData((prevData) => [...prevData, element]); 86 | }; 87 | 88 | // GSAP animations for slider transitions and video scroll triggers 89 | useGSAP(() => { 90 | gsap.to(SELECTORS.slider, { 91 | transform: `translateX(${-100 * videoId}%)`, 92 | duration: 2, 93 | ease: "power2.inOut", 94 | }); 95 | 96 | gsap.to(SELECTORS.video, { 97 | scrollTrigger: { 98 | trigger: SELECTORS.video, 99 | toggleActions: "restart none none none", 100 | }, 101 | onComplete: () => { 102 | setVideo((prevVideo) => ({ 103 | ...prevVideo, 104 | startPlay: true, 105 | isPlaying: true, 106 | })); 107 | }, 108 | }); 109 | }, [isEnd, videoId]); 110 | 111 | // Sync playback state with the video element 112 | useEffect(() => { 113 | if (loadedData.length > 3) { 114 | if (!isPlaying) { 115 | videoRef.current[videoId].pause(); 116 | } else { 117 | startPlay && videoRef.current[videoId].play(); 118 | } 119 | } 120 | }, [startPlay, videoId, isPlaying, loadedData]); 121 | 122 | // Animate progress bar for each video 123 | useEffect(() => { 124 | let currentProgress = 0; 125 | const span = videoSpanRef.current; 126 | 127 | if (span[videoId]) { 128 | const anim = gsap.to(span[videoId], { 129 | onUpdate: () => { 130 | const progress = Math.ceil(anim.progress() * 100); 131 | 132 | if (progress !== currentProgress) { 133 | currentProgress = progress; 134 | 135 | gsap.to(videoDivRef.current[videoId], { 136 | width: 137 | window.innerWidth < 760 138 | ? VIDEO_WIDTH.SMALL_SCREEN 139 | : window.innerWidth < 1200 140 | ? VIDEO_WIDTH.SMALL_SCREEN 141 | : VIDEO_WIDTH.LARGE_SCREEN, 142 | }); 143 | 144 | gsap.to(span[videoId], { 145 | width: `${currentProgress}%`, 146 | backgroundColor: BG_COLOR.PLAYING, 147 | }); 148 | } 149 | }, 150 | onComplete: () => { 151 | if (isPlaying) { 152 | gsap.to(videoDivRef.current[videoId], { 153 | width: VIDEO_WIDTH.DEFAULT, 154 | }); 155 | 156 | gsap.to(span[videoId], { 157 | backgroundColor: BG_COLOR.DEFAULT, 158 | }); 159 | } 160 | }, 161 | }); 162 | 163 | if (videoId === 0) { 164 | anim.restart(); 165 | } 166 | 167 | const animUpdate = () => { 168 | anim.progress( 169 | videoRef.current[videoId].currentTime / 170 | hightlightsSlides[videoId].videoDuration 171 | ); 172 | }; 173 | 174 | if (isPlaying) { 175 | gsap.ticker.add(animUpdate); 176 | } else { 177 | gsap.ticker.remove(animUpdate); 178 | } 179 | } 180 | }, [videoId, startPlay]); 181 | 182 | return ( 183 | <> 184 | {/* Video carousel slider */} 185 |
    186 | {hightlightsSlides.map((list, index) => ( 187 |
    192 |
    193 |
    194 | 224 |
    225 | 226 | {/* Video text overlay */} 227 |
    228 | {list.textLists.map((text) => ( 229 |

    233 | {text} 234 |

    235 | ))} 236 |
    237 |
    238 |
    239 | ))} 240 |
    241 | 242 | {/* Video controls */} 243 |
    244 |
    245 | {videoRef.current.map((_, index) => ( 246 | 249 | (videoDivRef.current[index] = element) 250 | } 251 | className="mx-2 w-3 h-3 bg-gray-200 rounded-full relative cursor-pointer" 252 | > 253 | 256 | (videoSpanRef.current[index] = element) 257 | } 258 | /> 259 | 260 | ))} 261 |
    262 | 273 |
    274 | 275 | ); 276 | }; 277 | 278 | export default VideoCarousel; 279 | -------------------------------------------------------------------------------- /src/components/iphonemodel/IPhone.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | Auto-generated by: https://github.com/pmndrs/gltfjsx 3 | Author: polyman (https://sketchfab.com/Polyman_3D) 4 | License: CC-BY-4.0 (http://creativecommons.org/licenses/by/4.0/) 5 | Source: https://sketchfab.com/3d-models/apple-iphone-15-pro-max-black-df17520841214c1792fb8a44c6783ee7 6 | Title: Apple iPhone 15 Pro Max Black 7 | */ 8 | 9 | import * as THREE from 'three'; 10 | import React, { useEffect, useRef } from "react"; 11 | import { useGLTF, useTexture } from "@react-three/drei"; 12 | 13 | function Model(props) { 14 | const { nodes, materials } = useGLTF("/models/scene.glb"); 15 | 16 | const texture = useTexture(props.item.img); 17 | 18 | useEffect(() => { 19 | Object.entries(materials).forEach(([materialName, material]) => { 20 | // Only update materials that are not excluded 21 | if ( 22 | materialName !== "zFdeDaGNRwzccye" && 23 | materialName !== "ujsvqBWRMnqdwPx" && 24 | materialName !== "hUlRcbieVuIiOXG" && 25 | materialName !== "jlzuBkUzuJqgiAK" && 26 | materialName !== "xNrofRCqOXXHVZt" 27 | ) { 28 | material.color.set(new THREE.Color(props.item.color[0])); 29 | material.needsUpdate = true; 30 | } 31 | }); 32 | }, [props.item, materials]); // Only update when necessary 33 | 34 | 35 | 36 | return ( 37 | 38 | 45 | 52 | 59 | 66 | 73 | 80 | 87 | 94 | 101 | 108 | 115 | 122 | 129 | 136 | 143 | 150 | 151 | 152 | 159 | 166 | 173 | 180 | 187 | 194 | 201 | 208 | 215 | 222 | 229 | 236 | 243 | 250 | 257 | 258 | ); 259 | } 260 | 261 | export default Model; 262 | 263 | useGLTF.preload("/models/scene.glb"); -------------------------------------------------------------------------------- /src/components/iphonemodel/Lights.jsx: -------------------------------------------------------------------------------- 1 | import { Environment, Lightformer } from "@react-three/drei"; 2 | 3 | const Lights = () => { 4 | return ( 5 | // group different lights and lightformers. We can use group to organize lights, cameras, meshes, and other objects in the scene. 6 | 7 | {/** 8 | * @description Environment is used to create a background environment for the scene 9 | * https://github.com/pmndrs/drei?tab=readme-ov-file#environment 10 | */} 11 | 12 | 13 | {/** 14 | * @description Lightformer used to create custom lights with various shapes and properties in a 3D scene. 15 | * https://github.com/pmndrs/drei?tab=readme-ov-file#lightformer 16 | */} 17 | 24 | 31 | 38 | 39 | 40 | 41 | {/** 42 | * @description spotLight is used to create a light source positioned at a specific point 43 | * in the scene that emits light in a specific direction. 44 | * https://threejs.org/docs/#api/en/lights/SpotLight 45 | */} 46 | 54 | 62 | 69 | 70 | ); 71 | }; 72 | 73 | export default Lights; -------------------------------------------------------------------------------- /src/components/iphonemodel/Model.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useRef, useEffect } from 'react'; 2 | import { useGSAP } from '@gsap/react'; 3 | import gsap from 'gsap'; 4 | import ModelView from './ModelView'; 5 | import { yellowImg } from '../../utils'; 6 | import * as THREE from 'three'; 7 | import { Canvas } from "@react-three/fiber"; 8 | import { View } from "@react-three/drei"; 9 | import { models, sizes } from '../../constants/index'; 10 | import { animateWithGsapTimeline } from '../../utils/animations'; 11 | 12 | 13 | const SMALL_SIZE = "small"; 14 | const LARGE_SIZE = "large"; 15 | const TRANSLATE_X_LARGE = "translateX(-100%)"; 16 | const TRANSLATE_X_SMALL = "translateX(0)"; 17 | const TIMELINE_ANIMATION_DURATION = 2; 18 | 19 | const SIZE_BUTTON_ACTIVE_STYLE = { 20 | backgroundColor: 'white', color: 'black' 21 | }; 22 | const SIZE_BUTTON_INACTIVE_STYLE = { 23 | backgroundColor: 'transparent', color: 'white' 24 | }; 25 | 26 | const Model = () => { 27 | const [size, setSize] = useState(SMALL_SIZE); 28 | const [model, setModel] = useState({ 29 | title: "iPhone 15 Pro in Natural Titanium", 30 | color: ["#8F8A81", "#FFE7B9", "#6F6C64"], 31 | img: yellowImg, 32 | }); 33 | 34 | // References for camera control and model groups 35 | const cameraControlSmall = useRef(); 36 | const cameraControlLarge = useRef(); 37 | const smallGroup = useRef(new THREE.Group()); 38 | const largeGroup = useRef(new THREE.Group()); 39 | 40 | // Rotation states 41 | const [smallRotation, setSmallRotation] = useState(0); 42 | const [largeRotation, setLargeRotation] = useState(0); 43 | 44 | const timeline = gsap.timeline(); 45 | 46 | useEffect(() => { 47 | if (size === LARGE_SIZE) { 48 | animateWithGsapTimeline( 49 | timeline, 50 | smallGroup, 51 | smallRotation, 52 | "#view1", 53 | "#view2", 54 | { 55 | transform: TRANSLATE_X_LARGE, 56 | duration: TIMELINE_ANIMATION_DURATION, 57 | } 58 | ); 59 | } else if (size === SMALL_SIZE) { 60 | animateWithGsapTimeline( 61 | timeline, 62 | largeGroup, 63 | largeRotation, 64 | "#view2", 65 | "#view1", 66 | { 67 | transform: TRANSLATE_X_SMALL, 68 | duration: TIMELINE_ANIMATION_DURATION, 69 | } 70 | ); 71 | } 72 | }, [size]); 73 | 74 | useGSAP(() => { 75 | gsap.to("#heading", { y: 0, opacity: 1 }); 76 | }, []); 77 | 78 | return ( 79 |
    80 |
    81 |

    82 | Take a closer look. 83 |

    84 | 85 |
    86 |
    87 | 96 | 97 | 106 | 107 | 119 | 120 | 121 |
    122 | 123 |
    124 |

    {model.title}

    125 | 126 |
    127 |
      128 | {models.map((item, index) => ( 129 |
    • setModel(item)} 134 | /> 135 | ))} 136 |
    137 | 138 | 154 |
    155 |
    156 |
    157 |
    158 |
    159 | ); 160 | }; 161 | 162 | export default Model; 163 | -------------------------------------------------------------------------------- /src/components/iphonemodel/ModelView.jsx: -------------------------------------------------------------------------------- 1 | import { Html, OrbitControls, PerspectiveCamera, View } from "@react-three/drei"; 2 | import Lights from "./Lights"; 3 | import IPhone from "./IPhone"; 4 | import { Suspense } from "react"; 5 | import * as THREE from "three"; 6 | import Loader from "../loader/Loader"; 7 | 8 | 9 | const SMALL_NAME = "small"; 10 | const LARGE_NAME = "large"; 11 | const VIEW_BASE = "w-full h-full absolute"; 12 | const RIGHT_CLASS = "right-[-100%]"; 13 | 14 | const SMALL_SCALE = [14, 14, 14]; 15 | const LARGE_SCALE = [16, 16, 16]; 16 | const GROUP_POSITION = [0, 0, 0]; 17 | const CAMERA_POSITION = [0, 0, 4]; 18 | const AMBIENT_LIGHT_INTENSITY = 0.3; 19 | 20 | const ORBIT_CONTROL_SETTINGS = { 21 | enableZoom: false, 22 | enablePan: false, 23 | rotateSpeed: 0.4, 24 | target: new THREE.Vector3(0, 0, 0), 25 | }; 26 | 27 | 28 | const ModelView = ({ index, groupRef, gsapType, controlRef, setRotationState, size, item }) => { 29 | const isIndexTwo = index === 2; 30 | const isIndexOne = index === 1; 31 | 32 | return ( 33 | 38 | {/* Ambient Light */} 39 | 40 | 41 | {/* Perspective Camera */} 42 | 43 | 44 | {/* Scene Lights */} 45 | 46 | 47 | {/* Orbit Controls */} 48 | setRotationState(controlRef.current.getAzimuthalAngle())} 53 | /> 54 | 55 | {/* 3D Model Group */} 56 | 61 | }> 62 | 67 | 68 | 69 | 70 | ); 71 | }; 72 | 73 | export default ModelView; 74 | -------------------------------------------------------------------------------- /src/components/loader/AppLoader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { appleImg } from "../../utils"; 3 | 4 | const AppLoader = ({ fadeOut }) => { 5 | return ( 6 |
    7 | {/* Logo */} 8 | Apple Logo 14 | 15 | {/* Loader container */} 16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 | ); 23 | }; 24 | 25 | export default AppLoader; 26 | -------------------------------------------------------------------------------- /src/components/loader/Loader.css: -------------------------------------------------------------------------------- 1 | .loader-container { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | } 7 | 8 | .line-loader { 9 | width: 80px; 10 | height: 4px; 11 | background-color: rgba(0, 0, 0, 0.2); 12 | overflow: hidden; 13 | border-radius: 5px; 14 | position: relative; 15 | margin-bottom: 10px; 16 | } 17 | 18 | .line { 19 | width: 30%; 20 | height: 100%; 21 | background-color: #fff; 22 | position: absolute; 23 | animation: slide 800ms infinite ease-in-out; 24 | } 25 | 26 | @keyframes slide { 27 | 0% { 28 | left: -30%; /* Start off-screen */ 29 | } 30 | 50% { 31 | left: 50%; /* Move across the container */ 32 | } 33 | 100% { 34 | left: 100%; /* Exit the container */ 35 | } 36 | } 37 | 38 | /* Fade in animation for the entire loader container */ 39 | @keyframes fadeIn { 40 | 0% { 41 | opacity: 0; 42 | } 43 | 100% { 44 | opacity: 1; 45 | } 46 | } 47 | 48 | /* Fade out animation after loader is complete */ 49 | @keyframes fadeOut { 50 | 0% { 51 | opacity: 1; 52 | } 53 | 100% { 54 | opacity: 0; 55 | } 56 | } 57 | 58 | /* Apply fade-out effect when loading is done */ 59 | .fade-out { 60 | animation: fadeOut 800ms ease-out forwards; 61 | } 62 | 63 | .loading-text { 64 | font-size: 1.4rem; 65 | color: #fff; 66 | } 67 | -------------------------------------------------------------------------------- /src/components/loader/Loader.jsx: -------------------------------------------------------------------------------- 1 | import { Html } from '@react-three/drei'; 2 | import React from 'react'; 3 | import './Loader.css'; 4 | 5 | const Loader = () => { 6 | return ( 7 | 9 |
    10 |
    11 |
    12 |
    13 | Loading... 14 |
    15 | 16 | ); 17 | }; 18 | 19 | export default Loader; 20 | -------------------------------------------------------------------------------- /src/constants/index.js: -------------------------------------------------------------------------------- 1 | // Import utilities 2 | import { 3 | blackImg, 4 | blueImg, 5 | highlightFirstVideo, 6 | highlightFourthVideo, 7 | highlightSecondVideo, 8 | highlightThirdVideo, 9 | whiteImg, 10 | yellowImg, 11 | } from "../utils"; 12 | 13 | // Navigation links 14 | export const navLists = ["Store", "Mac", "iPhone", "Support"]; 15 | 16 | // Highlight slides for promotional content 17 | export const hightlightsSlides = [ 18 | { 19 | id: 1, 20 | textLists: [ 21 | "Enter A17 Pro.", 22 | "Game-changing chip.", 23 | "Ground-breaking performance.", 24 | ], 25 | video: highlightFirstVideo, 26 | videoDuration: 4, 27 | }, 28 | { 29 | id: 2, 30 | textLists: ["Titanium.", "So strong. So light. So Pro."], 31 | video: highlightSecondVideo, 32 | videoDuration: 5, 33 | }, 34 | { 35 | id: 3, 36 | textLists: [ 37 | "iPhone 15 Pro Max has the", 38 | "longest optical zoom in", 39 | "iPhone ever. Far out.", 40 | ], 41 | video: highlightThirdVideo, 42 | videoDuration: 2, 43 | }, 44 | { 45 | id: 4, 46 | textLists: ["All-new Action button.", "What will yours do?"], 47 | video: highlightFourthVideo, 48 | videoDuration: 3.63, 49 | }, 50 | ]; 51 | 52 | // iPhone 15 Pro models with details 53 | export const models = [ 54 | { 55 | id: 1, 56 | title: "iPhone 15 Pro in Natural Titanium", 57 | color: ["#8F8A81", "#ffe7b9", "#6f6c64"], 58 | img: yellowImg, 59 | }, 60 | { 61 | id: 2, 62 | title: "iPhone 15 Pro in Blue Titanium", 63 | color: ["#53596E", "#6395ff", "#21242e"], 64 | img: blueImg, 65 | }, 66 | { 67 | id: 3, 68 | title: "iPhone 15 Pro in White Titanium", 69 | color: ["#C9C8C2", "#ffffff", "#C9C8C2"], 70 | img: whiteImg, 71 | }, 72 | { 73 | id: 4, 74 | title: "iPhone 15 Pro in Black Titanium", 75 | color: ["#454749", "#3b3b3b", "#181819"], 76 | img: blackImg, 77 | }, 78 | ]; 79 | 80 | // Available sizes for iPhone 15 Pro 81 | export const sizes = [ 82 | { label: '6.1"', value: "small" }, 83 | { label: '6.7"', value: "large" }, 84 | ]; 85 | 86 | // Footer links for the website 87 | export const footerLinks = [ 88 | "Privacy Policy", 89 | "Terms of Use", 90 | "Sales Policy", 91 | "Legal", 92 | "Site Map", 93 | ]; 94 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | * { 6 | margin: 0; 7 | padding: 0; 8 | box-sizing: border-box; 9 | } 10 | 11 | body { 12 | color: white; 13 | width: 100dvw; 14 | overflow-x: hidden; 15 | height: 100%; 16 | background: #000; 17 | border-color: #3b3b3b; 18 | user-select: none; 19 | } 20 | 21 | canvas { 22 | touch-action: none; 23 | } 24 | 25 | .screen-max-width { 26 | margin-inline-start: auto; 27 | margin-inline-end: auto; 28 | position: relative; 29 | max-width: 1120px; 30 | } 31 | 32 | @layer utilities { 33 | .flex-center { 34 | @apply flex items-center justify-center 35 | } 36 | 37 | .nav-height { 38 | @apply h-[calc(100vh-60px)] 39 | } 40 | 41 | .btn { 42 | @apply px-5 py-2 rounded-full bg-blue my-5 hover:bg-transparent border border-transparent hover:border hover:text-blue hover:border-blue text-lg; 43 | text-shadow: 1px 1px 2px #3b3b3b; 44 | } 45 | 46 | .color-container { 47 | @apply flex items-center justify-center px-4 py-4 rounded-full bg-gray-300 backdrop-blur 48 | } 49 | 50 | .size-btn-container { 51 | @apply flex items-center justify-center p-1 rounded-full bg-gray-300 backdrop-blur ml-3 gap-1 font-semibold 52 | } 53 | 54 | .size-btn { 55 | @apply w-10 h-10 text-sm flex justify-center items-center bg-white text-black rounded-full transition-all 56 | } 57 | 58 | .common-padding { 59 | @apply sm:py-32 py-20 sm:px-10 px-5 60 | } 61 | 62 | .section-heading { 63 | @apply text-gray lg:text-6xl md:text-5xl text-3xl lg:mb-0 mb-5 font-medium opacity-0 translate-y-20 64 | } 65 | 66 | .feature-text { 67 | @apply text-gray max-w-md text-lg md:text-xl font-semibold opacity-0 translate-y-[100px] 68 | } 69 | 70 | .feature-text-container { 71 | @apply w-full flex-center flex-col md:flex-row mt-10 md:mt-16 gap-5 72 | } 73 | 74 | .feature-video { 75 | @apply w-full h-full object-cover object-center scale-150 opacity-0 76 | } 77 | 78 | .feature-video-container { 79 | @apply w-full flex flex-col md:flex-row gap-5 items-center 80 | } 81 | 82 | .link { 83 | @apply text-blue hover:underline cursor-pointer flex items-center text-xl opacity-0 translate-y-20 84 | } 85 | 86 | .control-btn { 87 | @apply ml-4 p-4 rounded-full bg-gray-300 backdrop-blur flex-center 88 | } 89 | 90 | .hero-title { 91 | @apply text-center font-semibold text-3xl text-gray-100 opacity-0 max-md:mb-10 scale-110 92 | } 93 | 94 | .hiw-title { 95 | @apply text-4xl md:text-7xl font-semibold text-center 96 | } 97 | 98 | .hiw-subtitle { 99 | @apply text-gray font-semibold text-xl md:text-2xl py-10 text-center 100 | } 101 | 102 | .hiw-video { 103 | @apply absolute w-[95%] h-[90%] rounded-[56px] overflow-hidden 104 | } 105 | 106 | .hiw-text-container { 107 | @apply flex md:flex-row flex-col justify-between items-start gap-24 108 | } 109 | 110 | .hiw-text { 111 | @apply text-gray text-xl font-normal md:font-semibold 112 | } 113 | 114 | .hiw-bigtext { 115 | @apply text-white text-3xl md:text-5xl font-normal md:font-semibold my-2 116 | } 117 | 118 | .video-carousel_container { 119 | @apply relative sm:w-[70vw] w-[88vw] md:h-[70vh] sm:h-[50vh] h-[35vh] 120 | } 121 | 122 | .g_fadeIn { 123 | @apply opacity-0 translate-y-[100px] 124 | } 125 | 126 | .carousel-text{ 127 | @apply text-2xl font-normal md:font-medium; 128 | text-shadow: 1px 1px 2px #000; 129 | color: gainsboro; 130 | } 131 | } -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.jsx' 5 | 6 | createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /src/utils/animations.js: -------------------------------------------------------------------------------- 1 | import gsap from "gsap"; 2 | import { ScrollTrigger } from "gsap/all"; 3 | 4 | gsap.registerPlugin(ScrollTrigger); 5 | 6 | // Function to animate a target with GSAP 7 | export const animateWithGsap = (target, animationProps, scrollProps) => { 8 | gsap.to(target, { 9 | ...animationProps, 10 | scrollTrigger: { 11 | trigger: target, 12 | toggleActions: "restart reverse restart reverse", 13 | start: "top 85%", 14 | ...scrollProps, 15 | }, 16 | }); 17 | }; 18 | 19 | // Function to animate with a GSAP timeline 20 | export const animateWithGsapTimeline = ( 21 | timeline, 22 | rotationRef, 23 | rotationState, 24 | firstTarget, 25 | secondTarget, 26 | animationProps 27 | ) => { 28 | // Animate rotation 29 | timeline.to(rotationRef.current.rotation, { 30 | y: rotationState, 31 | duration: 1, 32 | ease: "power2.inOut", 33 | }); 34 | 35 | // Animate the first target 36 | timeline.to( 37 | firstTarget, 38 | { 39 | ...animationProps, 40 | ease: "power2.inOut", 41 | }, 42 | "<" 43 | ); 44 | 45 | // Animate the second target 46 | timeline.to( 47 | secondTarget, 48 | { 49 | ...animationProps, 50 | ease: "power2.inOut", 51 | }, 52 | "<" 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | import hero from "/assets/images/hero.jpeg"; 2 | 3 | export const heroImg = hero; 4 | 5 | import hmv from "/assets/videos/hero.mp4"; 6 | import smallmv from "/assets/videos/smallHero.mp4"; 7 | import highlightFirstmv from "/assets/videos/highlight-first.mp4"; 8 | import highlightSectmv from "/assets/videos/hightlight-third.mp4"; 9 | import highlightThirdmv from "/assets/videos/hightlight-sec.mp4"; 10 | import highlightFourthmv from "/assets/videos/hightlight-fourth.mp4"; 11 | import exploremv from "/assets/videos/explore.mp4"; 12 | import framemv from "/assets/videos/frame.mp4"; 13 | 14 | import apple from "/assets/images/apple.svg"; 15 | import search from "/assets/images/search.svg"; 16 | import bag from "/assets/images/bag.svg"; 17 | import watch from "/assets/images/watch.svg"; 18 | import right from "/assets/images/right.svg"; 19 | import replay from "/assets/images/replay.svg"; 20 | import play from "/assets/images/play.svg"; 21 | import pause from "/assets/images/pause.svg"; 22 | 23 | import yellow from "/assets/images/yellow.jpg"; 24 | import blue from "/assets/images/blue.jpg"; 25 | import white from "/assets/images/white.jpg"; 26 | import black from "/assets/images/black.jpg"; 27 | import explore1 from "/assets/images/explore1.jpg"; 28 | import explore2 from "/assets/images/explore2.jpg"; 29 | import chip from "/assets/images/chip.jpeg"; 30 | import frame from "/assets/images/frame.png"; 31 | 32 | export const heroVideo = hmv; 33 | export const smallHeroVideo = smallmv; 34 | export const highlightFirstVideo = highlightFirstmv; 35 | export const highlightSecondVideo = highlightSectmv; 36 | export const highlightThirdVideo = highlightThirdmv; 37 | export const highlightFourthVideo = highlightFourthmv; 38 | export const exploreVideo = exploremv; 39 | export const frameVideo = framemv; 40 | 41 | export const appleImg = apple; 42 | export const searchImg = search; 43 | export const bagImg = bag; 44 | export const watchImg = watch; 45 | export const rightImg = right; 46 | export const replayImg = replay; 47 | export const playImg = play; 48 | export const pauseImg = pause; 49 | 50 | export const yellowImg = yellow; 51 | export const blueImg = blue; 52 | export const whiteImg = white; 53 | export const blackImg = black; 54 | export const explore1Img = explore1; 55 | export const explore2Img = explore2; 56 | export const chipImg = chip; 57 | export const frameImg = frame; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: { 9 | colors:{ 10 | blue: "#2997FF", 11 | gray: { 12 | DEFAULT: "#86868b", 13 | 100: "#94928d", 14 | 200: "#afafaf", 15 | 300: "#42424570", 16 | }, 17 | zinc: "#101010", 18 | } 19 | }, 20 | }, 21 | plugins: [], 22 | } 23 | 24 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | --------------------------------------------------------------------------------