├── .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 |
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 |
40 | >
41 | )}
42 |
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 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
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 |
63 |
64 |
65 |
66 |
67 |
68 | {/* Call to Action */}
69 |
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 |
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 |
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 |
78 |
79 |
80 |
88 |
89 |
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 |
19 | {/* Logo */}
20 |
21 |
22 | {/* Navigation List */}
23 |
24 | {renderNavItems()}
25 |
26 |
27 | {/* Utility Icons */}
28 |
29 |
30 |
31 |
32 |
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 |
202 | (videoRef.current[index] = element)
203 | }
204 | onPlay={() => {
205 | setVideo((prevVideo) => ({
206 | ...prevVideo,
207 | isPlaying: true,
208 | }));
209 | }}
210 | onEnded={() =>
211 | index !== 3
212 | ? handleVideoEnd(index)
213 | : handleVideoLast()
214 | }
215 | onLoadedMetadata={(element) =>
216 | handleLoadedMetaData(index, element)
217 | }
218 | >
219 |
223 |
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 |
263 |
272 |
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 |
139 | {sizes.map(({ label, value }) => (
140 | setSize(value)}
149 | >
150 | {label}
151 |
152 | ))}
153 |
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 |
14 |
15 | {/* Loader container */}
16 |
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 |
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 |
--------------------------------------------------------------------------------