YouTube2Text

Build and Deploy a Unique 3D Web Developer Portfolio with React, Three JS & GSAP — Transcript

by JavaScript Mastery · 33,454 words · 4,749 segments · language en · Watch on YouTube

Full transcript

  1. 0:00Have you ever looked at a sleek 3D
  2. 0:02portfolio and thought, "I wish mine
  3. 0:04could look that good." Well, today is
  4. 0:07your lucky day. Hi there, and welcome to
  5. 0:10a course in which you'll build a
  6. 0:12portfolio that could easily get featured
  7. 0:15on awards, combining stunning 3D
  8. 0:17animations with a user-friendly layout
  9. 0:20that'll have recruiters and clients
  10. 0:22saying, "Wo, this guy really knows his
  11. 0:26stuff." with a minimal yet effective
  12. 0:29navigation bar. A stunning hero section
  13. 0:31with a 3D room model, particles,
  14. 0:34animated text, and a clear call to
  15. 0:36action and stylish cards showcasing your
  16. 0:39experience, projects, and clients. After
  17. 0:41that, you'll display your project in a
  18. 0:44clean bento grid layout featuring your
  19. 0:46best project. Then there's a unique and
  20. 0:49animated timeline that visually
  21. 0:51showcases your career journey, sleek
  22. 0:54cards to highlight the technologies
  23. 0:56you've mastered with interactive 3D
  24. 0:58icons, a testimonial grid with this cool
  25. 1:01hover effect, and finally, a functional
  26. 1:04contact section featuring a form and a
  27. 1:07computer desk 3D model like you're
  28. 1:09waiting for an email and finishing it
  29. 1:11off with a simple footer at the end. By
  30. 1:14the end of this course, you'll have a
  31. 1:16portfolio that not only looks amazing,
  32. 1:19but loads smoothly, even on mobile,
  33. 1:21showcasing your skills and experience in
  34. 1:24a way that's impossible to ignore. And
  35. 1:26if you're worried this might be too
  36. 1:28advanced, well, don't worry because I'll
  37. 1:31walk you through everything step by step
  38. 1:33from integrating 3D models with 3JS and
  39. 1:36React 3 fiber to adding smooth
  40. 1:38animations with Gap and building a sleek
  41. 1:42modern design with Tailwind CSS. So
  42. 1:44whether you're a beginner looking to
  43. 1:46level up your portfolio or an
  44. 1:48experienced developer aiming for the
  45. 1:50next big opportunity, this video is for
  46. 1:52you. And alongside this video, I've also
  47. 1:55put together a handy cheat sheet for 3JS
  48. 1:58development. You can grab it for free
  49. 2:00within the video kit in the description
  50. 2:02below and use it as a quick reference.
  51. 2:04And if you're looking to deepen your
  52. 2:06understanding of 3JS and turn it into a
  53. 2:09career, I recently put together a course
  54. 2:12focused entirely on vanilla 3JS from the
  55. 2:15basics to advanced interactive
  56. 2:17experiences. You'll get handson with
  57. 2:20live code playgrounds built right into
  58. 2:23our new platform, allowing you to
  59. 2:24experience with lighting, animations,
  60. 2:27and different 3D properties in real
  61. 2:29time. You'll instantly see the impact of
  62. 2:32every change truly learning how 3JS
  63. 2:35works under the hood. If that sounds
  64. 2:37helpful, the link is in the description.
  65. 2:39But with that said, are you ready to
  66. 2:41build a portfolio that's truly next
  67. 2:44level?
  68. 2:50In this course, I won't just show you
  69. 2:52how to build a portfolio. I'll also
  70. 2:55teach you how to deploy it to the
  71. 2:56internet so everyone can see what you
  72. 2:58built. Now, a developer portfolio is not
  73. 3:02like any other project. It is a career
  74. 3:05asset designed to showcase everything
  75. 3:08you're capable of, making you an
  76. 3:10attractive candidate for any job. And
  77. 3:13that's why I'll teach you how to go a
  78. 3:16step
  79. 3:17further. See, hosting your portfolio on
  80. 3:20a personalized domain makes you more
  81. 3:22professional, memorable, and improves
  82. 3:25your credibility and search engine
  83. 3:27visibility. So, before we dive into
  84. 3:30coding, let's get that domain name. I
  85. 3:32highly recommend Hostinger for hosting
  86. 3:35your modern portfolio. And right now,
  87. 3:38they're offering a great deal which you
  88. 3:40don't want to miss. When it comes to
  89. 3:42hosting this modern portfolio, I
  90. 3:44personally go for the premium plan. And
  91. 3:47here's why it's perfect for this and
  92. 3:49many other projects. You can host 100
  93. 3:52websites with the same account. You get
  94. 3:54a free domain name, which gives you a
  95. 3:56professional touch. You get a free SSL
  96. 3:59for HTTPS security, which creates trust
  97. 4:02with anyone visiting your portfolio.
  98. 4:05This deal won't last forever. And
  99. 4:07because I've partnered with Hostinger,
  100. 4:09you get an even bigger discount. Once
  101. 4:11you click the link in the description,
  102. 4:13click claim deal and add to cart. Here,
  103. 4:17we'll have to choose the period of our
  104. 4:18hosting. With the crazy discount going
  105. 4:21on right now, I'll definitely choose 48
  106. 4:23months to save the most money and enter
  107. 4:27your JavaScript Mastery coupon code,
  108. 4:29which will give you an even bigger
  109. 4:30discount on your hosting package. And
  110. 4:33once you buy it, we are ready to set
  111. 4:35everything up and get started with
  112. 4:37building out our portfolio. Let's start
  113. 4:39with Hostinger's guided setup. We are
  114. 4:41building a new website, so we can choose
  115. 4:43create. And we're building a completely
  116. 4:45custom website, so we can choose skip at
  117. 4:47the bottom. Next, we need to choose our
  118. 4:50domain. As you can see, I use Hostinger
  119. 4:52for my jsmastery.pro platform. So here
  120. 4:56you can type out your domain name. I
  121. 4:58would recommend something like your
  122. 4:59firstname and
  123. 5:01lastname.com something like
  124. 5:05johndoey.com and if you purchase the
  125. 5:07hosting for more than one year you'll
  126. 5:09get the domain completely for free.
  127. 5:11Choose it and click next. The domain is
  128. 5:13registering. Now already we are the next
  129. 5:15step where you can choose where your
  130. 5:17audience is located at and you can see
  131. 5:19there's a lot of options. Click next and
  132. 5:22the setup will initialize. While that is
  133. 5:25happening, just another quick note, and
  134. 5:27that is that the full codebase of the
  135. 5:28video you're about to build is live on
  136. 5:31GitHub. So, you can check it out right
  137. 5:32here. And if you don't mind, give it a
  138. 5:35star. With that in mind, we are
  139. 5:37redirected to the dashboard of our
  140. 5:38hosting project. So, finally, let's code
  141. 5:41it out and we'll deploy it at the end of
  142. 5:43the course.
  143. 5:46To get started developing our amazing 3D
  144. 5:49portfolio, we'll start by spinning up
  145. 5:52our Vit and React project. At this
  146. 5:55point, I think Vit is the preferred way
  147. 5:57to start React applications. So, just
  148. 6:00head over to vit.dev, click get started,
  149. 6:04scroll down a bit, and you'll be able to
  150. 6:06see a command that we have to run. So,
  151. 6:08let me show you how to do that right
  152. 6:10within your code editor. Or maybe you're
  153. 6:12using an IDE like I am. Throughout this
  154. 6:15course, I'll be using WebStorm, a super
  155. 6:18powerful IDE that is, as of recently,
  156. 6:21completely free for non-commercial use,
  157. 6:24which means that you can just download
  158. 6:25it and start using all of its amazing
  159. 6:27features, you can totally proceed with
  160. 6:28this course with any other code editor.
  161. 6:30But later on, once you see something
  162. 6:32cool happening right here inside of my
  163. 6:34IDE and it's not happening for you,
  164. 6:36well, that's most likely WebStorm. So,
  165. 6:39let's open up our integrated terminal
  166. 6:41and run mpm
  167. 6:43create vit latest and then the name of
  168. 6:48your project. If you're not currently
  169. 6:49within any kind of a folder, you might
  170. 6:51want to type 3D portfolio right here.
  171. 6:54But as I'm currently within a 3D
  172. 6:55portfolio folder, I'll just say dot
  173. 6:58slash to create the project in the
  174. 7:00current repository. And I'll press
  175. 7:02enter. It'll ask me a couple of
  176. 7:04questions such as do I want to install
  177. 7:06the create V installer to which I'll say
  178. 7:09why sure go ahead and then it's going to
  179. 7:11ask me a couple of questions. The
  180. 7:13current directory is not empty. Well, in
  181. 7:16this case I'll say remove existing files
  182. 7:19and continue. You can choose your
  183. 7:22package name. I'll just say 3D
  184. 7:24portfolio. And then you'll need to
  185. 7:26choose a framework or a flavor of your
  186. 7:28application. You can totally proceed
  187. 7:30with any option from the list, but
  188. 7:33throughout this course, I'll go with
  189. 7:35React and I'll go for the JavaScript
  190. 7:38variant. But of course, if you want to
  191. 7:39do it in TypeScript, you're totally free
  192. 7:41to do so. That's it. Simply run mpm
  193. 7:44install to install all the necessary
  194. 7:46dependencies to run a React application.
  195. 7:49And once that is done, run mpm rundev to
  196. 7:52spin up the project on localhost 5173.
  197. 7:55And then just click on it to open it up
  198. 7:57within your browser. You should be able
  199. 7:59to see something that looks like this.
  200. 8:01But now, let's clean up our file and
  201. 8:03folder structure a bit so everything
  202. 8:05makes a bit more sense for the build of
  203. 8:07our 3D portfolio. I'll start by diving
  204. 8:10into the source folder, removing all the
  205. 8:12assets we have right now. Removing the
  206. 8:14app CSS as well as app
  207. 8:19JSX. We'll create all of these on our
  208. 8:21own. And you'll want to clean up your
  209. 8:23index.css as we'll update it later on.
  210. 8:26Now to actually show something on the
  211. 8:28screen, we have to create a new
  212. 8:31app.jsx and within it run rafce to
  213. 8:36quickly create a new functional
  214. 8:37component that'll look something like
  215. 8:39this. If rafce didn't work for you, that
  216. 8:43might mean that you don't have the
  217. 8:44necessary extension or a plug-in
  218. 8:46installed. So go to your editor
  219. 8:49settings, either plugins or extensions,
  220. 8:52and search for React snippets. In this
  221. 8:55case, I'm using modern React snippets,
  222. 8:57but any should do the trick. Once you do
  223. 8:59that, we can remove this import as it's
  224. 9:02not necessary. And we can wrap
  225. 9:04everything within a main tag. Within it,
  226. 9:06we can render a div. And within this
  227. 9:09div, we can render an H1. That'll say
  228. 9:11something like welcome to my 3D
  229. 9:15portfolio. Perfect. And if you go back
  230. 9:17to your application, you should be able
  231. 9:19to see something that looks like Oh. Oh,
  232. 9:22wait. That's too light of a screen. We
  233. 9:24were looking at a dark screen for such a
  234. 9:26long time. So, sorry for flashing you
  235. 9:27with this light screen. But it is what
  236. 9:30it is. We'll switch it over to dark mode
  237. 9:32very
  238. 9:33soon. For now, the most important part
  239. 9:35is that you can actually see this empty
  240. 9:37canvas, which throughout the period of
  241. 9:39this course together will turn into your
  242. 9:423D portfolio. But to do it, we'll need
  243. 9:44some packages or dependencies that will
  244. 9:47help us work with 3D. So open up your
  245. 9:50terminal one more time and actually open
  246. 9:54up another one. This one we'll keep
  247. 9:56running our application and the second
  248. 9:58one we'll use for installing those
  249. 10:00additional
  250. 10:01dependencies. So run mpm install. And
  251. 10:05now we can list out all the packages and
  252. 10:07dependencies we'll be using. Let's start
  253. 10:09with
  254. 10:11gap. So what is gap? Well, if you head
  255. 10:14over to gap.com, you'll be able to see
  256. 10:16that it says animate anything. A widely
  257. 10:19robust JavaScript animation library
  258. 10:22built for professionals. It allows you
  259. 10:24to effortlessly animate anything that JS
  260. 10:27can touch, delivering silky smooth
  261. 10:30performance and unmatched support. Okay.
  262. 10:33Um I think they're trying to be a bit
  263. 10:35flashy right here and show you
  264. 10:37everything that you can do with GSAP
  265. 10:39right here on this landing page. And I
  266. 10:43gotta say, it's looking great. There's a
  267. 10:46showcase of many different projects that
  268. 10:48people have built over the years. And
  269. 10:51who knows, maybe your portfolio ends up
  270. 10:54right here. So, let's install it by
  271. 10:56installing Gap as well as
  272. 11:00Gap/React. Now, after installing our
  273. 11:03animations library, which we'll use to
  274. 11:05animate specific parts of our portfolio,
  275. 11:08you'll also want to install 3JS, a
  276. 11:11library for creating WebGL powered 3D
  277. 11:14scenes inside of your browser. If you
  278. 11:17are super new to 3JS, I would recommend
  279. 11:20checking out our free YouTube crash
  280. 11:22course before proceeding with this
  281. 11:24course or even checking out our vanilla
  282. 11:273JS course on JSMy Pro. If you check out
  283. 11:31the terminal, you'll see we dive super
  284. 11:33deep into everything that 3JS offers.
  285. 11:36Starting from things like cameras and
  286. 11:38orbiting different models all the way to
  287. 11:41transforming and moving your objects to
  288. 11:44adding meshes and geometries, managing
  289. 11:46it all through a graphical user
  290. 11:48interface and of course lightning and so
  291. 11:51much more because we're continuously
  292. 11:53adding new content to this course. But
  293. 11:56with that said, let's add three as well
  294. 11:58as at
  295. 12:01React-3/Fiber. Fiber is a React version
  296. 12:04of 3JS that simplifies the use of 3JS
  297. 12:09library within a React
  298. 12:12ecosystem. And also on top of that,
  299. 12:14there is a package called React
  300. 12:183/Dre. It is a helper library that even
  301. 12:22further simplifies the most common 3D
  302. 12:24tasks like lighting, loaders, and more.
  303. 12:28Finally, for some scenes, we'll also
  304. 12:30need some visual effects like bloom and
  305. 12:33so on. So, I'll say add
  306. 12:39react-3/postprocessing. I'll explain how
  307. 12:41all of that works pretty soon. For now,
  308. 12:43let's just get them installed by
  309. 12:45pressing the enter key. And with that,
  310. 12:47our first step is done. You have
  311. 12:48successfully created a React application
  312. 12:51with an empty page that right now looks
  313. 12:54something like this. And you have
  314. 12:56installed all the necessary packages.
  315. 12:58There aren't many. They're going to help
  316. 13:00you develop your 3D portfolio. Great
  317. 13:04work. So, what you say that we go from
  318. 13:07something that looks like this to
  319. 13:09something that looks a bit more like
  320. 13:11that. Well, to achieve that, we need a
  321. 13:14way to apply styling to our application.
  322. 13:17And for styling, we'll use Tailwind CSS,
  323. 13:20which allows you to rapidly build modern
  324. 13:22websites without ever leaving your HTML.
  325. 13:25In other words, it's a utility first CSS
  326. 13:28framework packed with utility classes
  327. 13:30for easy but fully customizable styling.
  328. 13:34Something that material UI, Bootstrap,
  329. 13:36or other styling component libraries
  330. 13:38don't do very well, which is
  331. 13:40customization. Here you're writing
  332. 13:43Tailwind which is technically still just
  333. 13:45CSS but in a much faster way. So let's
  334. 13:48go ahead and set it up. Head over to
  335. 13:51docs installation using vit and just
  336. 13:54copy this first command right here and
  337. 13:57paste it into our second terminal mpm
  338. 14:00install tailwind CSS and add tailwind
  339. 14:04CSS/vit. After that, we'll have to go
  340. 14:06over to the second step, which is to
  341. 14:09configure the Vit plugin by adding T and
  342. 14:12CSS V plugin to our V configuration. So,
  343. 14:15just copy this entire block of code,
  344. 14:18navigate over to V config. By the way, I
  345. 14:21just pressed commandP, which allowed me
  346. 14:24to quickly open up the file explorer and
  347. 14:27navigate to this file without ever
  348. 14:29having to search for it right here. So
  349. 14:32that is command P and now you can simply
  350. 14:35paste what you just copied where now
  351. 14:38we're setting the T and CSS plugin and
  352. 14:40we also need to keep the previous plugin
  353. 14:42that we had which was react that looks
  354. 14:45something like this and we have to
  355. 14:47import this react from
  356. 14:53atvjs/plugin- react perfect now the
  357. 14:56third step is to add an import to our
  358. 14:59CSS file that imports tailenc CSS. So
  359. 15:02copy this and paste it right at the top
  360. 15:05of our index.css file. Add import
  361. 15:08tailwind CSS. And here we can also
  362. 15:11define the base styles like the styles
  363. 15:14for our HTML and body elements and give
  364. 15:18it a width of something like 100 VW
  365. 15:21which stands for viewport width. We can
  366. 15:24also give it a background color of black
  367. 15:27and we can also give it a color of
  368. 15:30white.
  369. 15:31If you save this and go back, you should
  370. 15:33be able to see something that looks like
  371. 15:35this. Welcome to my 3D portfolio now
  372. 15:37with a dark background and light text.
  373. 15:40Now, in Tailwind CSS V4 and beyond, we
  374. 15:44now have the native support for at theme
  375. 15:47directive, which allows you to define
  376. 15:49some global styles for fonts or colors
  377. 15:52right within the index. CSS without
  378. 15:54having to have a separate
  379. 15:57Tailwind.config.js file. So here you can
  380. 15:59do something like
  381. 16:01theme-ash
  382. 16:05color-white-50 and you can define a
  383. 16:08specific value like hash D9 EC FFF. This
  384. 16:14is a different variation of the white
  385. 16:16color. And instead of specifying it
  386. 16:18every single time D9 EC FFF, now you can
  387. 16:22just say color white 50. But where did
  388. 16:25this color come from in the first place?
  389. 16:27Why did I use this specific color? Well,
  390. 16:30alongside watching this course, I'll
  391. 16:32also give you access to the entire
  392. 16:34codebase of the course in case you want
  393. 16:36to just look at the code. But more
  394. 16:38importantly, I'll give you full and free
  395. 16:40access to the finalized Figma design.
  396. 16:44So, in case you want to check it out and
  397. 16:46try to attempt developing it on your own
  398. 16:48or you want to change some things up,
  399. 16:50well, you can do that by checking out
  400. 16:51the final Figma design. The link to the
  401. 16:54video kit containing everything you need
  402. 16:56for this video, such as the source code,
  403. 16:58the Figma design, and additional assets
  404. 17:00and snippets, is right below this
  405. 17:03course. So, just click on it, get to the
  406. 17:05video kit, and then go to the Figma
  407. 17:07design. From within the design, if you
  408. 17:09inspect specific elements of text,
  409. 17:11you'll see different colors such as this
  410. 17:13button for example that has this silver
  411. 17:15color or this one right here. You can
  412. 17:18see D9 E CFF. This is the exact color
  413. 17:22that I have added to my tailwind config.
  414. 17:24So what you can do next is go through
  415. 17:26the entire Figma design and manually
  416. 17:28apply all of these colors such as for
  417. 17:30example dash color-b
  418. 17:34blue- which is hash 2D
  419. 17:382D38. And then you can also add all of
  420. 17:40the other colors as well. Alongside the
  421. 17:43theming functionalities, you can also
  422. 17:45add the custom utility classes using
  423. 17:48Tailwind's layer feature. So you can say
  424. 17:50add layer
  425. 17:52utilities and then you can define for
  426. 17:54example a flex- center class name. Why
  427. 17:59flex center? Because so often we need to
  428. 18:02center something within a div, right? So
  429. 18:04instead of every time saying flex center
  430. 18:07center here you can just
  431. 18:09define that this flex center will apply
  432. 18:13a property of flex a justify center and
  433. 18:17items center. So when you want to use it
  434. 18:19within the code, it is super simple. You
  435. 18:22just say flex center. And there are many
  436. 18:23other ways in which we can use this new
  437. 18:25tailwind configuration to make your life
  438. 18:28much easier later on. So just so you
  439. 18:30don't have to go over to our design and
  440. 18:33poke every single element 10 times until
  441. 18:35you get to its color, I'll provide the
  442. 18:37final index.css file for you right at
  443. 18:40the bottom of this video kit. So head
  444. 18:42over there and copy the index.css file.
  445. 18:44Once you copy it, override everything we
  446. 18:47wrote here and simply paste it. You'll
  447. 18:50notice that here we add some additional
  448. 18:52elements that are going to help us style
  449. 18:54our application as well as some basic
  450. 18:57elements for defining different grids,
  451. 19:01responsiveness, and utility functions as
  452. 19:03well as most importantly theming. And
  453. 19:07we're adding our own font as well from
  454. 19:09Google Fonts. This doesn't contain any
  455. 19:12programming logic or 3JS code. Rather,
  456. 19:14it's just some utility classes that will
  457. 19:16make the styling of our application
  458. 19:18easier. Whenever there's something we're
  459. 19:20using within our code, I'll point your
  460. 19:22attention to it. For example, take a
  461. 19:24look at these components. This layer of
  462. 19:26components is used to define custom
  463. 19:29styles scoped to real components. This
  464. 19:32keeps things modular. For example, if
  465. 19:34you search for something like a navbar
  466. 19:37right here, you can see that as soon as
  467. 19:39we apply class name of navbar, it's
  468. 19:41going to apply these classes right here
  469. 19:43to make it look nice. And you can also
  470. 19:45immediately style the logo within the
  471. 19:47nav or even how list items within it
  472. 19:50look like. These styles act like
  473. 19:52reusable building blocks across the app.
  474. 19:54Think of this as the design system layer
  475. 19:57where you store the visual identity of
  476. 19:59the buttons, layouts, grids, and more.
  477. 20:03Don't worry, I'm going to explain how it
  478. 20:04all works very soon. And the only thing
  479. 20:06you'll need other than the styles are
  480. 20:09going to be the assets. Yep, these
  481. 20:11little icons that you can see here,
  482. 20:13here, here, even here. I want to make
  483. 20:17sure that you have everything you need
  484. 20:19to continue developing this application
  485. 20:21without going back and forth with the
  486. 20:22design. So, I've took the time and put
  487. 20:25all of these icons in a single zipped
  488. 20:28folder. Head over to the video kit and
  489. 20:30find it right here. Once you download
  490. 20:32it, head over to your current
  491. 20:33application and delete the current
  492. 20:35public
  493. 20:36folder. Unzip the folder you just
  494. 20:38downloaded and drag and drop this new
  495. 20:41public folder right within your
  496. 20:43application. This one will contain a lot
  497. 20:46of images, logos, and textures as well
  498. 20:49as different 3D models we'll use
  499. 20:51throughout our application. With that
  500. 20:52said, let's test whether our Tailwind
  501. 20:54CSS configuration works. I'll pull my
  502. 20:57browser to the side so we can see it
  503. 20:58alongside our IDE and I'll open up the
  504. 21:02source app.jsx and I'll apply a class
  505. 21:05name of
  506. 21:08text-3 Excel. If you do that and if your
  507. 21:11text gets larger or if you maybe apply
  508. 21:13an underline that means that Tailwind is
  509. 21:16properly connected to your application.
  510. 21:18And with that we are ready to start
  511. 21:20working on the hero section. So let's do
  512. 21:23that next.
  513. 21:26The portfolio hero section. The first
  514. 21:29thing that your users see and a major
  515. 21:32chance to make a first impression. Here
  516. 21:34we'll start strong by immediately
  517. 21:37blending some UI elements, scrollbased
  518. 21:40interactivity on the navbar and a
  519. 21:42realtime 3D canvas. So let me start by
  520. 21:45breaking it down into two major parts.
  521. 21:48First the JSX structure and the layout
  522. 21:51and then of course the GEP animations
  523. 21:54and interactivity. First things first,
  524. 21:56we have to organize our projects into
  525. 21:59sections and components. So let me clean
  526. 22:01up this app.jsx and organize our
  527. 22:04project. We can start by creating a
  528. 22:06components folder right within the
  529. 22:08source folder. I'll call it components
  530. 22:11and I'll also create another which I'll
  531. 22:14call sections. Next, head over into
  532. 22:17sections and create a new file called
  533. 22:21hero.jsx and run rafce and then import
  534. 22:25it within your app.jsx. That'll look
  535. 22:28something like this. We have our app. We
  536. 22:31can wrap it just in an empty react
  537. 22:33fragment for now and render a
  538. 22:35self-closing hero section just like
  539. 22:38this. But make sure to import it from
  540. 22:40sections hero.jsx. If you do that, you
  541. 22:43should be able to see just the hero
  542. 22:44right here on the right.
  543. 22:46But now we can start diving deeper into
  544. 22:48that hero section by creating our hero
  545. 22:51section layout. First we'll start with
  546. 22:54some semantic HTML by turning this div
  547. 22:58into a section. We can give the section
  548. 23:00an ID so we can later on scroll to it of
  549. 23:03hero and a class name of relative and
  550. 23:08overflow-hidden. Inside of it, we can
  551. 23:10create a div that'll have a class name
  552. 23:13of absolute
  553. 23:16top-0 left-0 and a zindex of 10. What is
  554. 23:21this for? Well, for a little background
  555. 23:24element that I want to show near the top
  556. 23:26left of the screen. So, let me render a
  557. 23:28self-closing image tag with a source of
  558. 23:31for/
  559. 23:34images/bg.png with an al tag of
  560. 23:37background. If you save it, you won't be
  561. 23:39able to see anything yet. But soon
  562. 23:41enough, the goal is to show these little
  563. 23:43dots right here floating on top left of
  564. 23:46the screen. So, right below this div,
  565. 23:48let's set up our main layout by creating
  566. 23:51a div with a class name equal to hero-
  567. 23:56layout. On the left side, we will
  568. 23:59display our hero content, meaning the
  569. 24:02text, and on the right side, we'll
  570. 24:04display the 3D model. So start by
  571. 24:07creating a semantic header
  572. 24:10tag with a class name of flex flex- call
  573. 24:16justify- center on medium devices w-
  574. 24:20full so it takes full screen
  575. 24:23wcreen and on medium devices padding x
  576. 24:26of 20 and padding x of five usually this
  577. 24:30is to make sure that it looks good on
  578. 24:32mobile devices as well. And now you can
  579. 24:34start seeing that background appear
  580. 24:36right here. Within it, we can display a
  581. 24:38div that'll have a class name equal to
  582. 24:42flex, flex- call, and a gap of seven.
  583. 24:46And now we can focus on the main
  584. 24:48headline. Yep, this is the one that I'm
  585. 24:50talking about. It'll actually be
  586. 24:52comprised of three different H1
  587. 24:54elements. So let's first start by
  588. 24:57creating a new div for it. It'll have a
  589. 24:59class name of
  590. 25:02hero-ext. And then here we can create
  591. 25:04our first H1 of shaping. Below it we can
  592. 25:08say into real projects. And below it we
  593. 25:11can say that deliver results. But we're
  594. 25:16shaping what? But what are we shaping?
  595. 25:20Well, if you take a look at the final
  596. 25:21application, you'll see that here we'll
  597. 25:24have animated text that says ideas,
  598. 25:27code, designs, concepts, and more. So,
  599. 25:31for now, let's just use one of these.
  600. 25:32For example, designs. And later on, I'll
  601. 25:36show you how we can make it animated.
  602. 25:38First, we need to define some kind of an
  603. 25:40array that we want to loop over. We
  604. 25:42might as well define it here by saying
  605. 25:44const words is equal to an array of
  606. 25:48different objects. And each word has to
  607. 25:51have a text, for example, ideas. And
  608. 25:54then we also have to give it an image
  609. 25:56path because each word will also have a
  610. 26:00little icon that you can see alongside
  611. 26:02that word. So let's give it an image
  612. 26:04path of forward slash
  613. 26:10images/id.svg. And you can do the same
  614. 26:12thing with the second one which is going
  615. 26:14to be
  616. 26:15concepts. And here you can see
  617. 26:17concepts.svg that SVG. Perfect. So now
  618. 26:21I'm going to zoom this out so it seems
  619. 26:23more like a real application. And I want
  620. 26:26to figure out how we can animate it.
  621. 26:28Well, first of all, let's map over those
  622. 26:30words right here. So after the word
  623. 26:33shaping, instead of designs, I'll open
  624. 26:36up a new span
  625. 26:38element. It'll look like this. A span
  626. 26:42with a class name equal to slide.
  627. 26:46within it another span with a class name
  628. 26:50equal to
  629. 26:51wrapper and within it we want to map
  630. 26:54over our words by saying words.m map
  631. 26:58where we get each individual word and
  632. 27:00for each word we want to automatically
  633. 27:03return a span. Now what do I mean when I
  634. 27:06say automatically return? I mean not
  635. 27:09open up a curly brace here, rather just
  636. 27:12open up a parenthesy because that means
  637. 27:14an immediate return.
  638. 27:17This span since we're mapping over it
  639. 27:19has to have a key of something like
  640. 27:24word.ext because each text is unique and
  641. 27:27a class name of flex items center on
  642. 27:31medium devices a gap of three typically
  643. 27:34a gap of one and a padding bottom of two
  644. 27:38just to create some spacing. Finally
  645. 27:40within that span we can render an image
  646. 27:42for the icon. It'll have a source of
  647. 27:44word img path. It'll have an al tag of
  648. 27:50word.ext and it'll have a class name of
  649. 27:55on extra-large devices size of 12. On
  650. 27:58medium devices, size of 10 and typically
  651. 28:02a size of seven. So it looks good on all
  652. 28:05different screen sizes. On medium
  653. 28:07devices, we can also give it some
  654. 28:08padding of two. typically a padding of
  655. 28:12one rounded dash full so it sits within
  656. 28:15a circle with a bg of 50. So if I save
  657. 28:20this you can now see two different icons
  658. 28:23that are animating up and down. And of
  659. 28:27course alongside the icon right below it
  660. 28:30I want to display another span element
  661. 28:32that'll simply render the
  662. 28:35word.ext. So now it says ideas and it
  663. 28:38animates over to concepts. But keeping
  664. 28:41this list of different words right here
  665. 28:44on top is not that ideal because what if
  666. 28:47we want to have not just two different
  667. 28:49words but rather five, six or 10. It
  668. 28:53doesn't make sense to clutter the view
  669. 28:55of the hero with some data points. And
  670. 28:57as a matter of fact, if you wanted
  671. 28:59somebody to very easily be able to
  672. 29:01update this, doesn't make sense to keep
  673. 29:03it here. It's better to extract all
  674. 29:05static data into a standalone file
  675. 29:08typically called constants which
  676. 29:10contains nothing more than just some
  677. 29:12data that we want to use across our
  678. 29:14application. So let's copy these words
  679. 29:18and instead of keeping them here let's
  680. 29:20create a new file within the source
  681. 29:23folder or rather a new folder called
  682. 29:26constants and within constants create a
  683. 29:29new file called index.js
  684. 29:32JS within it. Paste those words and make
  685. 29:35sure to export them by saying export
  686. 29:38const words words. And now when using
  687. 29:41those words, you can import them over
  688. 29:44from the constants. Everything still
  689. 29:46works as it did, but now we have just a
  690. 29:48single import at the top. And now if you
  691. 29:51wanted to provide more words right here,
  692. 29:53you can do that easily. As a matter of
  693. 29:55fact, we can add or shape concepts,
  694. 29:59designs, code, ideas, and I went ahead
  695. 30:02and duplicated it right here just so we
  696. 30:04have more things to scroll over in a
  697. 30:06more dynamic way. And then when it
  698. 30:09reaches the end, it'll very nicely
  699. 30:11scroll all the way to the first one.
  700. 30:14There we go. Super nice, right? So
  701. 30:17throughout this application, there's
  702. 30:18going to be more cases where we want to
  703. 30:20have some static code like arrays of
  704. 30:22different data that we want to use. And
  705. 30:24you can find those at the bottom of the
  706. 30:26video kit as well. Just copy the
  707. 30:28constants index.js file and just paste
  708. 30:31it here. You'll notice that we have
  709. 30:34different words, but then also you can
  710. 30:36change your abilities. For example, what
  711. 30:38are your strengths and descriptions of
  712. 30:40those strengths? Which different images
  713. 30:43do we want to import? Maybe you can also
  714. 30:45change different navigation links. It is
  715. 30:47all right here. So now that we have
  716. 30:49that, I want to take a second to explain
  717. 30:52how this animation works. See this span
  718. 30:55has a class name of slide and then a
  719. 30:58span inside of it has a class name of
  720. 31:00wrapper. These classes are defined
  721. 31:03within the index.css file. So if you
  722. 31:06commandclick them, I think this should
  723. 31:08lead you directly to where they're
  724. 31:10mentioned or you can just find them
  725. 31:12within the index.css. CSS and here you
  726. 31:14can see that we're defining the display
  727. 31:17of the entire block as inline block. We
  728. 31:20set the direction of column and then we
  729. 31:22start a transition. The key part right
  730. 31:25here is this key frames word slider
  731. 31:29where we're translating the Y position
  732. 31:31of the different words right here such
  733. 31:33as designs, code and so on. First by a
  734. 31:37little then more and then more and then
  735. 31:40we finally fully transform it. and then
  736. 31:42we animated over a specific number of
  737. 31:44seconds infinitely. This subtle yet
  738. 31:47powerful micro interaction brings life
  739. 31:50to your main headline and immediately
  740. 31:53communicates what you do both visually
  741. 31:56and interactively. And then below this
  742. 31:58slide with the wrapper H1, we can also
  743. 32:02add a P tag that can say something like,
  744. 32:06"Hi, I'm Adrian, a developer based in
  745. 32:10Croatia with a passion for code." Of
  746. 32:14course, you can type something
  747. 32:15different. Make sure to give this P tag
  748. 32:18a class name of text-w50.
  749. 32:22on medium devices text-
  750. 32:25XL relative a Z of 10 and pointer events
  751. 32:30none to make it non-clickable. And of
  752. 32:33course, this was supposed to be outside
  753. 32:35of the H1, not just the first one, but
  754. 32:38all of them. So, I'll post it right
  755. 32:40below this div that says hero text.
  756. 32:44There we go. Much better. Finally, below
  757. 32:46this, we want to create some kind of a
  758. 32:48call to action where people can click on
  759. 32:51a button and then that button leads us
  760. 32:54to our work. This button will be reused
  761. 32:57many times throughout our application.
  762. 32:59So, it makes most sense to create a
  763. 33:02reusable button component. So, back in
  764. 33:04our application, let's head over into
  765. 33:06the components folder and create our
  766. 33:09first component called button.jsx.
  767. 33:13This button will be used across multiple
  768. 33:15sections both in the hero and contact
  769. 33:18section. So let's make it fully reusable
  770. 33:20and dynamic. I'll run rafce to quickly
  771. 33:24spin up our functional component. And
  772. 33:26then within it we can make this button
  773. 33:29an anchor tag. This anchor tag will have
  774. 33:31a class name which will be equal to
  775. 33:35CTA-wrapper. CTA stands for call to
  776. 33:38action which means an action that you
  777. 33:39want your user to take. Before I
  778. 33:41continue developing this button, let's
  779. 33:43actually use it within our hero section.
  780. 33:45So, right below this B tag, I will
  781. 33:48render our button. Make sure to import
  782. 33:50it at the top. And then within this
  783. 33:52anchor tag, I can create a div with a
  784. 33:55class name equal to CTA group and group.
  785. 34:00And within it, we can render another
  786. 34:02div. This time, a self-closing one with
  787. 34:05a class name of
  788. 34:07BG- circle. Next, below it, we can
  789. 34:10render a P tag with a class name of
  790. 34:14text. And we can simply render something
  791. 34:17that should say like see my work. And
  792. 34:21there we go. See my work is right here.
  793. 34:23Below it, let's also render a div with a
  794. 34:26class
  795. 34:27name equal to arrow-wrapper.
  796. 34:32And let's render an image with a source
  797. 34:34of forward slash images slash
  798. 34:38arrow-down. SVG with an al tag of arrow.
  799. 34:42We cannot see it yet and that's because
  800. 34:44we have to style it a bit better. So
  801. 34:46from the hero section, let's pass some
  802. 34:48props that allow us to style it. I'll
  803. 34:51pass over a class name equal to on
  804. 34:54medium devices W of 80. on median
  805. 34:58devices H of 16 typically W60 and H of
  806. 35:0312. And why do we have to provide the
  807. 35:05class name as a prop and not immediately
  808. 35:07right here? Well, that's because this
  809. 35:09button is going to be used in different
  810. 35:11scenarios. And depending on where it's
  811. 35:13being used, we want to provide some
  812. 35:15different class names. We can also give
  813. 35:16it an ID of a button. And we can provide
  814. 35:19a text such as see my work because in
  815. 35:23the contact section, it's going to say
  816. 35:25something else. So now we can head over
  817. 35:27into that button and dstructure those
  818. 35:30props that we're passing into it such as
  819. 35:33the text, the class name, and the
  820. 35:37ID. Next, we can make this first class
  821. 35:40name dynamic. I'll delete it and make it
  822. 35:43dynamic by opening up a new dynamic
  823. 35:45block of code and a template string.
  824. 35:48I'll say if a class name exists. So
  825. 35:51class name and then question mark
  826. 35:53question mark else render an empty
  827. 35:55string. What does this mean? If a class
  828. 35:58name exists render it. If it doesn't
  829. 36:00render an empty string and then always
  830. 36:03render a CTA wrapper. Finally here
  831. 36:07instead of see my work we can render the
  832. 36:09text. And in this case, the class name
  833. 36:12here should have been CTA button instead
  834. 36:15of a CTA group, which will activate
  835. 36:17those classes. If you head over to that,
  836. 36:20you'll see that it'll apply some
  837. 36:21padding, both horizontal and vertical.
  838. 36:24It'll round the corners a bit, apply a
  839. 36:27dark background. But hey, if it's dark,
  840. 36:29why is it white right now? Well, that's
  841. 36:31because we also have a transition. So as
  842. 36:34you hover over it, you can see that the
  843. 36:36background turns dark and only the arrow
  844. 36:38jumps up and down. That happens because
  845. 36:41of these inner classes such as BG circle
  846. 36:43and the arrow wrapper. So with that in
  847. 36:45mind, our button is fully styled and
  848. 36:48later on once we implement a couple more
  849. 36:50sections, we'll actually make it scroll
  850. 36:52to the sections just below. Now we can
  851. 36:54get back to the hero section and see
  852. 36:57what we have. Right now, we have been
  853. 36:59working on the header component, which
  854. 37:01is actually just the left part of the
  855. 37:03hero content section. Or if you're
  856. 37:05looking at it on a mobile device, that
  857. 37:07is this top part. But now is the time to
  858. 37:10fill in this huge gap on the right side.
  859. 37:13And we can do that by introducing this
  860. 37:15great 3D room model. So, let's do that
  861. 37:19in the next
  862. 37:21lesson. to start adding our great 3D
  863. 37:24model right below or to the right side
  864. 37:26of the text. We can head back over into
  865. 37:29the hero section and we'll implement it
  866. 37:32right here below the header where it
  867. 37:35says right side 3D model. I wouldn't
  868. 37:37even call this a model. It's actually an
  869. 37:39entire 3D
  870. 37:41experience. So, let's go ahead and
  871. 37:43create this component. Right within
  872. 37:46source components, create a new folder
  873. 37:49and call it hero models. Within hero
  874. 37:52models, create a new file and call it
  875. 37:55hero
  876. 37:58experience.jsx. Just run rafce for now.
  877. 38:02Later on, we'll implement the entire
  878. 38:03experience here. But for now, import it
  879. 38:06right here where it says 3D model by
  880. 38:09putting it in a figure. Figure is an
  881. 38:12HTML 5 semantic tag that says that
  882. 38:14something important will be right here
  883. 38:17and it is self-contained meaning it is a
  884. 38:19single unit that we want to display
  885. 38:20within it. We can render a div that'll
  886. 38:23have a class name equal to hero-3d
  887. 38:28layout. This will give it 70% of the
  888. 38:30width of the screen on large devices or
  889. 38:33full width on smaller devices. So, if I
  890. 38:35head back right here and give this a
  891. 38:37border red of 200 and
  892. 38:41border-2, you should be able to see that
  893. 38:43it takes full width of the screen. But
  894. 38:45if we expand it, it's taking about 70%
  895. 38:48of the screen right here, which is still
  896. 38:50a lot to show this model. So, right
  897. 38:52within it, let's render this
  898. 38:54self-closing hero experience. This
  899. 38:57component is absolutely positioned. So,
  900. 38:59you can see it jumps a bit over here,
  901. 39:01but as we implement it, it's going to
  902. 39:03jump right below. So let's head over
  903. 39:05into it. And here we have to render our
  904. 39:07GB model. So what is a GB model? Well, a
  905. 39:11GB is a file format that allows us to
  906. 39:14render 3D scenes and models. You can
  907. 39:17find many of these models on websites
  908. 39:19like SketchFab. Some of these are free
  909. 39:22and some of them are paid and you can
  910. 39:24easily download them and put them right
  911. 39:26within your application. But let's take
  912. 39:28it slow. First things first, I want to
  913. 39:30set up a canvas because in 3JS
  914. 39:33everything begins with a canvas. So
  915. 39:35instead of a div, let me render a canvas
  916. 39:38which I have to import from React 3
  917. 39:41fiber. And we have to have a camera
  918. 39:43within that canvas. So I'll say camera
  919. 39:46is equal to and then we can set the
  920. 39:49position of the camera to an array of
  921. 39:51something like zero on the x-axis, zero
  922. 39:54on the y-axis, and about 15 on the
  923. 39:57z-axis, which is pulled back a bit,
  924. 39:59allowing us to frame the model nicely.
  925. 40:01We can also give it something known as
  926. 40:02an FOV or field of view of 45, allowing
  927. 40:06us to once again nicely frame it within
  928. 40:09our camera. And of course, we cannot yet
  929. 40:10see anything if we don't have a light.
  930. 40:12So let me add an
  931. 40:14ambient light which is a self-closing
  932. 40:17component with an intensity of something
  933. 40:20like 0.2. And we can also add a color to
  934. 40:23this light such as hash 1 a1 a40. If you
  935. 40:28save this, you won't be able to see
  936. 40:31anything just yet. Why? Well, because
  937. 40:34there's nothing to see. There's just
  938. 40:36light. So to see something within the
  939. 40:39canvas, you need to add a 3D object such
  940. 40:43as a mesh with a geometry and a
  941. 40:45material. Well, here's an example of
  942. 40:48adding a simple box. You can create a
  943. 40:51new mesh and then within it, you can
  944. 40:53render a box
  945. 40:56geometry with args an array of 11 one,
  946. 41:01which is how wide and how big it is. And
  947. 41:05then you can also give this mesh some
  948. 41:06kind of material such as mesh standard
  949. 41:10material and make it a color of
  950. 41:13something like
  951. 41:16blue. If you do this and save, you still
  952. 41:19won't be able to see it. And that's
  953. 41:21because we have to appropriately light
  954. 41:23our subject. Ambient light in this case
  955. 41:25is not enough. So, let me add a
  956. 41:28directional light with a position of
  957. 41:31something like five five and an
  958. 41:36intensity of one. If you do that, you
  959. 41:38should be able to see a blue or let's
  960. 41:41make it teal
  961. 41:43box. Pretty cool, right? But right now,
  962. 41:46it's just a 2D box. So, how can we
  963. 41:49actually move it around to see it in all
  964. 41:51of its three dimensions? Well, we can
  965. 41:53add something known as orbit controls,
  966. 41:56allowing us to move around this 3D
  967. 41:59model. I'll add it right here above the
  968. 42:02mesh by calling it orbit controls coming
  969. 42:05from React 3D. It is a self-closing
  970. 42:08component. And if you just call it like
  971. 42:10this, you'll already be able to move
  972. 42:12this box in all different dimensions.
  973. 42:15And you can even zoom it in. As you can
  974. 42:17see, currently only some sides are lit
  975. 42:19up while the others are not. You can
  976. 42:22play with the intensity of the light to
  977. 42:23see how that changes the model. And of
  978. 42:26course, this is something we dive much
  979. 42:27deeper into within our vanilla 3JS
  980. 42:29course. So, I just had to show this to
  981. 42:31you. Uh we go super deep into different
  982. 42:34types of lights that there are. And then
  983. 42:38check this out. You can see exactly what
  984. 42:40each 3JS property is doing right here
  985. 42:43within the lesson. I mean, just how cool
  986. 42:47is that? You can play with the intensity
  987. 42:49of different point lights, change the
  988. 42:52positions of the lights, and even change
  989. 42:54the rotations. We have a second point
  990. 42:56light. You can see how it gets reflected
  991. 42:58on different elements. Pretty cool
  992. 43:00stuff, right? And right now we are
  993. 43:02dealing with a directional light. We
  994. 43:04dive a bit deeper into the power
  995. 43:06property. And check this out. At the
  996. 43:08end, you can also practice your
  997. 43:10knowledge using a quiz. Pretty cool,
  998. 43:12right? And if something's not clear, you
  999. 43:14can ask questions right here within the
  1000. 43:16comment system. and I'll reply very
  1001. 43:18soon. But with that said, let's actually
  1002. 43:20make something out of this cube. Or
  1003. 43:22rather, should I say, we need to import
  1004. 43:24our real 3D model here. But before,
  1005. 43:27let's first play a bit with the orbit
  1006. 43:29controls. I will set the enable
  1007. 43:33pan to be set to false, which is going
  1008. 43:36to prevent panning the scene, so you can
  1009. 43:39only zoom in and zoom out, as well as
  1010. 43:41move around. Also, I want to enable the
  1011. 43:44zoom only if we're on desktop devices.
  1012. 43:47But if we're on tablet, then I want to
  1013. 43:49disable it because it's going to prevent
  1014. 43:51the users to be able to scroll
  1015. 43:52throughout the website. So I'll create a
  1016. 43:55dummy variable called is tablet and I'll
  1017. 43:59make it equal to false for now. So here
  1018. 44:02we can say if not is tablet then enable
  1019. 44:06zoom else disable it. Of course very
  1020. 44:09soon we'll have to make this work for
  1021. 44:11real but for now we're good. After that,
  1022. 44:13we can define the max distance that we
  1023. 44:16can zoom out. I'll set it to 20. As well
  1024. 44:20as min
  1025. 44:22distance, and I'll set that to about
  1026. 44:24five. We can also set the min polar
  1027. 44:27angle to
  1028. 44:29math.pi over 5, and a max polar angle to
  1029. 44:34math.pi over two. This allows us to
  1030. 44:37control where in the screen we can move.
  1031. 44:40For example, we can move up to this
  1032. 44:42point up. We cannot have a full view
  1033. 44:45down and a full view up in the room. We
  1034. 44:48can only look at it from this angle. And
  1035. 44:50that's exactly what these polar angles
  1036. 44:52are doing. While we're here, let's also
  1037. 44:54implement this is tablet functionality
  1038. 44:56for real. And for that, we'll use a
  1039. 44:59library called React Responsive. So,
  1040. 45:03I'll simply run mpmi
  1041. 45:06react-responsive to install it. And this
  1042. 45:08will allow us to ensure that on larger
  1043. 45:10devices, users can rotate and zoom while
  1044. 45:13mobile and tablet users will get a bit
  1045. 45:15of a simpler experience. Using this
  1046. 45:18library is super simple. You just have
  1047. 45:20to say is tablet is equal to use media
  1048. 45:24query and then you pass a query you want
  1049. 45:27to validate for such as max width of
  1050. 45:32768 pixels like this. And then for the
  1051. 45:36other one we can maybe define is mobile.
  1052. 45:38Is mobile will be max width about yeah I
  1053. 45:42think 768 and actually tablet is 124
  1054. 45:46like this. So now we have this real is
  1055. 45:49tablet variable. But now is the time
  1056. 45:51that instead of this fake cube which you
  1057. 45:54can see now we cannot zoom on mobile
  1058. 45:56devices which allows us to then move up
  1059. 45:58and down normally as we would on any
  1060. 46:00other website. But if I zoom in on
  1061. 46:03desktop, that still works. So now the
  1062. 46:05main goal is that instead of this cube,
  1063. 46:08we actually render this full 3D model.
  1064. 46:11While I was creating the design for this
  1065. 46:13amazing 3D portfolio, I was thinking for
  1066. 46:16a long time, which model should we use.
  1067. 46:20I went through many different
  1068. 46:22iterations, but ultimately ended up on
  1069. 46:24this one. It is a developer-like room.
  1070. 46:29It has a lamp, a PC, a robot vacuum, a
  1071. 46:33chair, and two screens. Pretty nice.
  1072. 46:36It's also not super realistic. It has a
  1073. 46:38nice artistic look. A bit of gloom from
  1074. 46:41these monitors shining away. And it just
  1075. 46:44feels like a developer's room, right,
  1076. 46:47which you want to put on your portfolio.
  1077. 46:49But of course, you're completely free to
  1078. 46:51search any 3D model you want. Sketch Fab
  1079. 46:54is one of the better websites. So, for
  1080. 46:57example, you can search for a laptop or
  1081. 47:00a MacBook model. There we go. And there
  1082. 47:03are many cool ones that you can use and
  1083. 47:05display in the hero of your application.
  1084. 47:07But, as I said, I really like this one,
  1085. 47:09and I think you will, too. So, let me
  1086. 47:12show you what the process is of using
  1087. 47:14some of these models in your apps. You
  1088. 47:16could totally go ahead and download it
  1089. 47:18right here. After you log in, it'll just
  1090. 47:21give you a GB file and then you would be
  1091. 47:24able to import it within your
  1092. 47:25application. But here's the thing, some
  1093. 47:28of these models are super large. For
  1094. 47:30example, the GB file is 1 megabyte
  1095. 47:33large. And even though that is not so
  1096. 47:36big, it has 33,000 triangles, which
  1097. 47:39makes it take some time to load. And
  1098. 47:41that is without the lights that we need
  1099. 47:43to display on it. So what I did is I
  1100. 47:46didn't want to use this model directly.
  1101. 47:48Rather, I asked my friend that knows a
  1102. 47:51bit of Blender for 3D modeling to clean
  1103. 47:54up unnecessary meshes, hitting
  1104. 47:56geometries, and parts that we just
  1105. 47:58didn't need on the screen. This allowed
  1106. 48:01us to reduce the number of triangles
  1107. 48:03while still preserving the original
  1108. 48:05detail. And they also grouped some parts
  1109. 48:07together, so it's easier for us to name
  1110. 48:10them and manage them within the code.
  1111. 48:12and she's actually teaching me those
  1112. 48:14Blender optimization techniques so I can
  1113. 48:16cover them within the Elite 3JS course
  1114. 48:19which will release soon on the platform.
  1115. 48:22But for now, just so you don't have to
  1116. 48:24go through that process yourself, I
  1117. 48:25decided to give you already optimized
  1118. 48:28models right here within the public
  1119. 48:30folder. So remember putting this here
  1120. 48:32before, if you go into models, you'll
  1121. 48:34see an optimized
  1122. 48:36room.gb. This is the model you want to
  1123. 48:39use. Now to be able to use any kind of a
  1124. 48:41GB model, you need to first convert it
  1125. 48:44to JSX by using the gltf JSX package. It
  1126. 48:48is a very simple package that turns gltf
  1127. 48:51files into JSX components. It is super
  1128. 48:55simple to use. Let me show you. I'll
  1129. 48:57open up my terminal and actually open up
  1130. 48:59a new one. Within this one, I will cd
  1131. 49:03into /public slmodels. So now I'm in the
  1132. 49:08same folder where we have all of these
  1133. 49:10models we'll end up using. The only
  1134. 49:12thing you have to do is run mpxg
  1135. 49:17gltfjsx optimized
  1136. 49:21room.gb and press
  1137. 49:24enter. It's asking you whether you want
  1138. 49:26to install that package. So say yes or
  1139. 49:29just y. And this will create a clean
  1140. 49:32optimized room.jsx JSX file for you
  1141. 49:35containing the scene hierarchy that we
  1142. 49:37can directly import and render within
  1143. 49:40our React 3 fiber project. Here it is
  1144. 49:44optimized
  1145. 49:45room.jsx. So simply move this file over
  1146. 49:49to source components hero models and put
  1147. 49:52it here right next to the hero
  1148. 49:55experience. Then head over into the
  1149. 49:57optimized room and rename the file to
  1150. 50:01something like room. You can also change
  1151. 50:03it right here at the top. Export
  1152. 50:05function room. And this was
  1153. 50:07autogenerated by gltf to
  1154. 50:10JSX. Pretty cool, right? So what is
  1155. 50:14this? Well, this is a JSX representation
  1156. 50:16of this 3D model. It has different
  1157. 50:19meshes and geometries that we can use
  1158. 50:22and all of that together will form that
  1159. 50:24room. Just make sure to point it to the
  1160. 50:27right file. Currently, it's pointing to
  1161. 50:29optimized room GB, but you'll have to
  1162. 50:31point it to forward
  1163. 50:34slashmodels/optimized roomg. And you'll
  1164. 50:37have to prepend that word models right
  1165. 50:39here as well because if you check out
  1166. 50:42our file and folder structure, you'll
  1167. 50:44see it's within public models and then
  1168. 50:46optimize room. Great. Now, let's import
  1169. 50:49this model into our hero experience.
  1170. 50:52I'll head over to that component and
  1171. 50:55instead of just rendering a mesh, a box
  1172. 50:58geometry, we can render this room. So,
  1173. 51:01I'll render a self-closing room
  1174. 51:03component coming from components room.
  1175. 51:06And if I save it, check this out. Even
  1176. 51:10on mobile, you cannot really zoom in and
  1177. 51:12out. And right now, it's not positioned
  1178. 51:15in the best way possible, but you
  1179. 51:18already get the idea for what this is.
  1180. 51:21In my case, it is a heavily lit room,
  1181. 51:23like there is a sun shining right here
  1182. 51:26from the middle of the room. The more
  1183. 51:29directional light intensity you add, the
  1184. 51:31brighter it'll be and the less you'll
  1185. 51:33see the colors. But if you reduce it,
  1186. 51:36for
  1187. 51:37example, already it'll start looking so
  1188. 51:40much better. Now, what do you say that
  1189. 51:42we more properly position this model on
  1190. 51:45our screen? So instead of just rendering
  1191. 51:48the room itself, I'll render it inside
  1192. 51:50of a group which then allows us to
  1193. 51:53adjust different things such as the
  1194. 51:56scale of the room. So I can say that if
  1195. 52:00we are on mobile devices then the scale
  1196. 52:02will be 0.7 times the original size else
  1197. 52:06it'll be the full size. So now the model
  1198. 52:09will be a bit easier to handle on mobile
  1199. 52:11devices. We can also change the
  1200. 52:15position where we can make it 0 on the
  1201. 52:18x-axis, minus 3.5 on yaxis and zero on
  1202. 52:23the z-axis to move it down a bit. There
  1203. 52:26we go. This makes more sense. And we can
  1204. 52:28also change the rotation a tiny bit by
  1205. 52:32making it equal to zero minus
  1206. 52:36math.pi over 4 and then zero on the
  1207. 52:40z-axis. This will rotate it slightly. So
  1208. 52:43once you first open it up, it should
  1209. 52:45look something like this. Nicely
  1210. 52:47positioned within your mobile screen.
  1211. 52:49Currently our lights are totally off,
  1212. 52:51but don't worry, we're going to fix it
  1213. 52:53soon. Before that, check it out on
  1214. 52:55desktop. Looking great. Well, soon
  1215. 52:58enough it'll look great. Lights truly do
  1216. 53:01make a big difference when working with
  1217. 53:033D models. So, let there be light.
  1218. 53:06Instead of simply adding lights one by
  1219. 53:08one like this, we can actually create a
  1220. 53:11new component within the hero models.
  1221. 53:14And I'll call it hero
  1222. 53:18lights.jsx. Run rafce and then simply
  1223. 53:21add the lights right within this empty
  1224. 53:23react fragment. For now, I will simply
  1225. 53:27use the same two lights that we had
  1226. 53:29here. So if I copy them and remove them
  1227. 53:31from here, save. Oh, just the screens
  1228. 53:34will light. This is pretty cool because
  1229. 53:36they emit their own light. Uh, but I'll
  1230. 53:40put those right here, ambient
  1231. 53:43directional. And now I will import the
  1232. 53:46hero lights right above this group. And
  1233. 53:49you can see we have light again. So now
  1234. 53:53we can head into the hero lights and
  1235. 53:55start implementing all the different
  1236. 53:56lights that we want to have in our
  1237. 53:58application. You might think that there
  1238. 54:00are not many lights right here, but the
  1239. 54:02more you think about it, the more lights
  1240. 54:03you'll see. I'll increase the width of
  1241. 54:06my browser a bit so we can see it a bit
  1242. 54:08better. Let's start by removing those
  1243. 54:10two lights and add a spot light just
  1244. 54:14like this. Make sure to spell the L in
  1245. 54:16spotlight
  1246. 54:18uppercased. And let's give it a
  1247. 54:20position. This one will be the front
  1248. 54:23right spotlight, which is going to mimic
  1249. 54:25a strong key light like the desk lamp
  1250. 54:28that we have right here. So, I'll
  1251. 54:30position it on two 56. And you might
  1252. 54:33want to play with this a bit just to
  1253. 54:35make sure to get it right. And let's of
  1254. 54:37course give it some kind of
  1255. 54:39intensity. I'll make it very strong,
  1256. 54:41like 100. And now you can see that it's
  1257. 54:44coming from the top right. But if you
  1258. 54:46play with these values a bit, like 2
  1259. 54:48to2, you'll see how strong it is right
  1260. 54:51now. Or we can make it come from the
  1261. 54:54center of the screen. 0
  1262. 54:560. You can't see anything then. But then
  1263. 54:59if you change it to 1
  1264. 55:01one, you can see how well it is lighting
  1265. 55:04everything. As you move the numbers a
  1266. 55:06bit like 135 or maybe 145 or maybe you
  1267. 55:11make it this two, you can see how this
  1268. 55:13light behaves. For now, I'll set it to
  1269. 55:18256. There we go. This is already
  1270. 55:20looking so much better. We can also
  1271. 55:24angle the spotlight a bit to maybe
  1272. 55:26something like 0.15. And check this out.
  1273. 55:29Get what I was saying? We are really
  1274. 55:32talking about a desk lamp light right
  1275. 55:35here. Yep. You can go to that level of
  1276. 55:37detail where only a small part of the
  1277. 55:41model is visible. There's also this
  1278. 55:43interesting property called penubra,
  1279. 55:45which basically means the softness
  1280. 55:47around the edges of that light. And you
  1281. 55:50can change the color of the light to
  1282. 55:52something like
  1283. 55:54white. There we go. Now let's add an
  1284. 55:57overhead lamp. I'll copy this initial
  1285. 55:59light. Change the position to four
  1286. 56:0454. Change the angle to
  1287. 56:070.3. The intensity to about 40. We don't
  1288. 56:10need it to be that strong. Penubra to
  1289. 56:12about 0.5. And we can give it a bit of a
  1290. 56:16cool blue tone. Let's say that we have
  1291. 56:18some colored LEDs. Something like 4 CC
  1292. 56:220F0. And now we're lighting this larger
  1293. 56:25part of the screen as well. Let's add
  1294. 56:27another one. And I mean, you can play
  1295. 56:28with this as much as you'd like. We
  1296. 56:30could have been totally happy with what
  1297. 56:32I had initially with just one
  1298. 56:34directional light, but I want to really
  1299. 56:36create the mood, if you know what I
  1300. 56:38mean. Next one, we'll have minus
  1301. 56:41three, five, and five. There we go. So
  1302. 56:45now you can notice how these lights
  1303. 56:47start overlapping. With an angle of 0.4,
  1304. 56:50four. We can give it a super soft edge
  1305. 56:53of just
  1306. 56:54one intensity to about
  1307. 56:5760 and a color of like
  1308. 57:029D4 ed which is going to give it a bit
  1309. 57:05of a different tone. And now I want to
  1310. 57:07add another type of light.
  1311. 57:10This one will be defined as primitive
  1312. 57:12because within primitive you can provide
  1313. 57:14any kind of an object here which will be
  1314. 57:17a new three dot wreck area light. So
  1315. 57:22this is a special type of light that
  1316. 57:25provides a soft and moody fill light.
  1317. 57:28You can also provide it a color like
  1318. 57:30A259 FF. I found that one to work pretty
  1319. 57:34well. Oh, and here I forgot to pass a
  1320. 57:37hash. So the color wasn't actually
  1321. 57:39getting applied. So if I comment out
  1322. 57:41this primitive light just so you can see
  1323. 57:43what was happening. This is looking so
  1324. 57:45much more interesting than before.
  1325. 57:47Right? So now we have this super white
  1326. 57:49light. We have this bluish spotlight
  1327. 57:52right here. And then we also have this
  1328. 57:55purplish spotlight at the right side.
  1329. 57:57And now we're adding this additional
  1330. 58:00wrecked area light. To be able to use
  1331. 58:02it, you have to first import at the
  1332. 58:05top. So I'll say
  1333. 58:08import asterisk as three from three. And
  1334. 58:13this allows you to use different
  1335. 58:15elements from the library. Of course,
  1336. 58:17this lights up everything a bit too
  1337. 58:20much, but you get what I'm going for,
  1338. 58:22right? We can change its
  1339. 58:25position to about 0 1 0. And we need to
  1340. 58:30change this
  1341. 58:32intensity to 15. And the rotation is the
  1342. 58:35most important part. We want to angle it
  1343. 58:37down toward the scene. So I'll change
  1344. 58:40the
  1345. 58:41rotation
  1346. 58:43to minus
  1347. 58:46math.pi over 4.
  1348. 58:49Math.pi over 4 and then zero for the
  1349. 58:52Z-axis. It's also important what else
  1350. 58:55you pass into this rect area light. See
  1351. 58:57the second property it takes in is the
  1352. 59:00intensity and then the width and the
  1353. 59:01height. So alongside the color I'm going
  1354. 59:04to provide eight as the intensity to
  1355. 59:07this wrecked area
  1356. 59:09light, three as the width and two as the
  1357. 59:12height. So that's going to angle it a
  1358. 59:14bit more toward the room. Finally, I
  1359. 59:17want to add a point light which is going
  1360. 59:19to be very subtle and scattered for
  1361. 59:22color balance. So I'll simply give it a
  1362. 59:26position of 0 1 0. So, it's going to be
  1363. 59:31on the center side, but a bit lower with
  1364. 59:34an
  1365. 59:35intensity of 10 and a color of hash
  1366. 59:42720
  1367. 59:449b7. I found that color to work very
  1368. 59:47well. And I'll add another point light
  1369. 59:50right below it by duplicating this one.
  1370. 59:53This one will come from behind or off to
  1371. 59:55the right. So that's going to be 1 2 and
  1372. 59:59minus two with an intensity of 10 and a
  1373. 1:00:03deep blue color like 0D 00 A4. So now
  1374. 1:00:07the lighting in this room looks pretty
  1375. 1:00:10crazy, doesn't it? I think one thing we
  1376. 1:00:12can do to tone it down a bit is properly
  1377. 1:00:15position this rectangular light by
  1378. 1:00:18changing the position to come from the
  1379. 1:00:20front right side.
  1380. 1:00:22So on X-axis one, a bit from the top, so
  1381. 1:00:26Y-axis three, and then four from the
  1382. 1:00:30Z-axis. You can see this now fills the
  1383. 1:00:32room a bit nicer. And maybe the most
  1384. 1:00:35important thing that I missed is the
  1385. 1:00:38softness around the edges of the light.
  1386. 1:00:41Even though I thought I applied it, you
  1387. 1:00:43can see that these lights seem to be
  1388. 1:00:45fighting with each other, one
  1389. 1:00:47overlapping with the other. And while
  1390. 1:00:49it's pretty cool to see that you can
  1391. 1:00:50test color theory by applying different
  1392. 1:00:52colored lights over the same surface
  1393. 1:00:54area, we don't want it in our
  1394. 1:00:57application. So when I said penumbra
  1395. 1:01:00here, I was actually missing an M. So if
  1396. 1:01:02you add it here, you'll notice how this
  1397. 1:01:04light will get much softer and it won't
  1398. 1:01:06compete with the rest of the lights.
  1399. 1:01:08Same thing for all the other two
  1400. 1:01:10spotlights. Yeah, I think the glow of
  1401. 1:01:13the lamp shows it the best because it
  1402. 1:01:15has such a high intensity. Check this
  1403. 1:01:18out. If I change the value of that
  1404. 1:01:20property to nothing, so it doesn't
  1405. 1:01:22exist, it'll have a very sharp light.
  1406. 1:01:26But if I change the value, it'll soften
  1407. 1:01:29it and more and more and more until it
  1408. 1:01:33becomes barely noticeable, but it is
  1409. 1:01:36still there. In this case, lamps really
  1410. 1:01:38do emit some light, so I want to leave
  1411. 1:01:41it right here. Pretty cool stuff, right?
  1412. 1:01:44But now everything still seems a bit too
  1413. 1:01:47purplish or bluish, doesn't it? It
  1414. 1:01:50doesn't seem like a real room. Doesn't
  1415. 1:01:53have that same artistic style that the
  1416. 1:01:55original model had. So, let's go outside
  1417. 1:01:57of the lights for a bit and let's go
  1418. 1:01:59back to the room model to see whether we
  1419. 1:02:02can fix some of these meshes, materials,
  1420. 1:02:05and geometries. I want to replace some
  1421. 1:02:08of these default materials with the ones
  1422. 1:02:11we're going to custom define that better
  1423. 1:02:13react to light. That way, we'll have
  1424. 1:02:16more control. For example, a fong
  1425. 1:02:19material, that's
  1426. 1:02:21fng, is great for soft shading with
  1427. 1:02:25subtle highlights, whereas the standard
  1428. 1:02:27material is great for realistic lighting
  1429. 1:02:30and reflections. So depending on what we
  1430. 1:02:32want to achieve, we can apply a
  1431. 1:02:34different material to different meshes.
  1432. 1:02:37The way it works is like this. You can
  1433. 1:02:40first import something known as use
  1434. 1:02:43texture by saying const matcap texture
  1435. 1:02:47is equal to use texture coming from
  1436. 1:02:52react 3J. And here you can define where
  1437. 1:02:55that texture is coming from. In this
  1438. 1:02:58case, we can go to forward slash images
  1439. 1:03:02slashextures
  1440. 1:03:05slmmath1.png. And if you try to take a
  1441. 1:03:08look at this math 1 image, you should be
  1442. 1:03:10able to find it under images textures
  1443. 1:03:13math 1. And it should look something
  1444. 1:03:15like this. We'll use it for some meshes
  1445. 1:03:17right here. And you'll soon see how
  1446. 1:03:19that'll look like. So just to show you a
  1447. 1:03:21simple example, I'll say const curtain
  1448. 1:03:25material and make it equal to a new
  1449. 1:03:293 mesh fong
  1450. 1:03:32material. And to it you can pass a color
  1451. 1:03:35of hash
  1452. 1:03:39D90429 as an example. And don't forget
  1453. 1:03:42to import everything as three from three
  1454. 1:03:47to be able to use these materials. Now
  1455. 1:03:51we can put that curtain material to use.
  1456. 1:03:54For example, in this first mesh geometry
  1457. 1:03:57where it says materials blend one, you
  1458. 1:03:59can use the curtain material and see
  1459. 1:04:02what difference does it make. Currently,
  1460. 1:04:04we haven't applied it properly, which is
  1461. 1:04:06actually good because you can see how
  1462. 1:04:08this mesh looks like without any
  1463. 1:04:11material. Super plain, right? But as
  1464. 1:04:13soon as I properly fix this color right
  1465. 1:04:15here by removing this space, you can see
  1466. 1:04:18that we have a completely different
  1467. 1:04:20story. We get a much more vibrant color
  1468. 1:04:23that reacts to light a bit differently.
  1469. 1:04:26So, this right here is the material that
  1470. 1:04:28we had before, blind one. Okay, pretty
  1471. 1:04:32cool, but a bit plain. And then this is
  1472. 1:04:35our new curtain material, which is
  1473. 1:04:37nothing special. It is just a basic
  1474. 1:04:40built-in 3GS mesh fong material with a
  1475. 1:04:43bright, vibrant color. And I know this
  1476. 1:04:45might seem like a lot because we're
  1477. 1:04:47diving into adding so many different
  1478. 1:04:50sorts of lights to make this room super
  1479. 1:04:54unique because let's be honest, light is
  1480. 1:04:56what allows you to see the room in the
  1481. 1:04:58first place. So, we tackled spotlights,
  1482. 1:05:01primitives with the wrecked area lights,
  1483. 1:05:04point lights, and more. And now we're
  1484. 1:05:06even changing the original materials of
  1485. 1:05:08some meshes right here. The one that's
  1486. 1:05:10going to make the biggest difference for
  1487. 1:05:12sure has to be this texture that we're
  1488. 1:05:15importing from a PNG. Yep, you can
  1489. 1:05:18import a texture from a PNG image. Let
  1490. 1:05:22me show you how. I'll say const body
  1491. 1:05:25material and make it equal to a new 3
  1492. 1:05:29mesh fong material to which I will pass
  1493. 1:05:34a map. So instead of a color you can
  1494. 1:05:37pass a map and I'll pass it as the matte
  1495. 1:05:40cap texture that I defined above from
  1496. 1:05:43image. And now we can apply this body
  1497. 1:05:46material right here to this second mesh.
  1498. 1:05:49Instead of materials blend, I'll apply a
  1499. 1:05:52body material and see if you can notice
  1500. 1:05:54the difference. It's the walls. See how
  1501. 1:05:57the walls react differently to lights
  1502. 1:06:00depending on which materials we give it.
  1503. 1:06:03This is cool, sure, but this definitely
  1504. 1:06:06makes it look a bit different if that's
  1505. 1:06:08what you're going for. Now, just so you
  1506. 1:06:10don't have to go ahead and update every
  1507. 1:06:12single material with its own color, it
  1508. 1:06:14is like being a bit of an artist, right?
  1509. 1:06:16Changing the colors. I'll provide you
  1510. 1:06:18with the final room.jsx file right
  1511. 1:06:21within the video kit. So head over here
  1512. 1:06:24at the bottom of the file and copy it
  1513. 1:06:27room.jsx. If you paste it, you'll notice
  1514. 1:06:30that we're leaving some materials as
  1515. 1:06:32blind. Some like the table we're
  1516. 1:06:34changing and then some more like the
  1517. 1:06:36radiator pillow material, maybe even
  1518. 1:06:40something like the chair or the
  1519. 1:06:41computer. That's what we need to change.
  1520. 1:06:44So, if I save this now and reload, we
  1521. 1:06:48get something that looks a bit more like
  1522. 1:06:50this. It's much closer to the final
  1523. 1:06:52effect that we were going for. I just
  1524. 1:06:54feel that this works with the original
  1525. 1:06:56theme of the portfolio that we're going
  1526. 1:06:58for. Oh, and notice one cool thing, and
  1527. 1:07:01that is that these monitors now have
  1528. 1:07:03this some kind of a glow. And that in
  1529. 1:07:06the world of 3JS is called a selective
  1530. 1:07:10bloom. So, I can change the intensity of
  1531. 1:07:12that bloom, and you'll notice how much
  1532. 1:07:15stronger it'll be. It like the light
  1533. 1:07:17bleeds out. So, I found the value of 1.5
  1534. 1:07:21to work the best. So, it seems like
  1535. 1:07:23they're shining too bright to be able to
  1536. 1:07:25see what's on them. Now, if you reload
  1537. 1:07:27the page on your desktop device, you'll
  1538. 1:07:30see the full 3D experience. We have a
  1539. 1:07:34what appears to be a super moody room
  1540. 1:07:36where a lot of vibe coding happens. But
  1541. 1:07:40once again, I invite you to play with
  1542. 1:07:43the lights a bit. Every change that you
  1543. 1:07:45make makes a big difference to the final
  1544. 1:07:47model. And in the final app, I even
  1545. 1:07:49added some particles. But to be honest,
  1546. 1:07:52I think that's a bit too much because
  1547. 1:07:55snow or particles don't really fall
  1548. 1:07:58within a room, right? Uh but if you do
  1549. 1:08:01want to add them, I'll leave it right at
  1550. 1:08:02the bottom of the video kit. The only
  1551. 1:08:04thing you have to do is create a
  1552. 1:08:07new component within the hero models.
  1553. 1:08:11Call it
  1554. 1:08:14particles.jsx. Paste that code right
  1555. 1:08:16here. And then get back over to your
  1556. 1:08:19hero experience. And then right below
  1557. 1:08:21the lights, you can render the
  1558. 1:08:25particles and pass the count of how many
  1559. 1:08:28particles you'd like. I'll set it to
  1560. 1:08:30about 100 and see the magic
  1561. 1:08:34happen. If you want to really freeze
  1562. 1:08:36your computer, set it to a thousand and
  1563. 1:08:39see what happens. There we go. That's a
  1564. 1:08:42sky full of stars if I've ever seen one.
  1565. 1:08:44Pretty cool, right? Again, either don't
  1566. 1:08:46include it at all as it might be a bit
  1567. 1:08:48of an overkill or leave it to about 100
  1568. 1:08:51max. So, with that in mind, great job on
  1569. 1:08:55completing the hero section. It has been
  1570. 1:08:58the toughest part of this portfolio.
  1571. 1:09:00Trust me on that. But at least you learn
  1572. 1:09:02a lot about
  1573. 1:09:06lights. Now, there is one more thing
  1574. 1:09:08regarding this hero that you might have
  1575. 1:09:09not noticed we're missing right now. As
  1576. 1:09:11opposed to how it is in the final
  1577. 1:09:13application. It is the loading animation
  1578. 1:09:15of this text. Even though this part is
  1579. 1:09:18always animating, once we reload, we
  1580. 1:09:20also want to animate each one of these
  1581. 1:09:22lines line by line to look something
  1582. 1:09:25like this. Check this out. The first one
  1583. 1:09:28will drop from the top first, then the
  1584. 1:09:30second, and then the third. Check this
  1585. 1:09:31out. As I reload the page, they all drop
  1586. 1:09:34one by one from the bottom to the top.
  1587. 1:09:38So, let me show you how to implement
  1588. 1:09:39that animation. This time, we won't be
  1589. 1:09:42using 3JS. Rather, we'll be using Gap.
  1590. 1:09:45So head over into your hero component
  1591. 1:09:48and let's
  1592. 1:09:50import use GSAP coming from gap react as
  1593. 1:09:56well as import gap which is the general
  1594. 1:09:59library from gap just like this. Then
  1595. 1:10:02inside of the component we can call the
  1596. 1:10:05use gap hook to animate this h1 tags. We
  1597. 1:10:09can do it by saying use gap to it. You
  1598. 1:10:11have to pass a callback function that we
  1599. 1:10:13want to activate. And there you can use
  1600. 1:10:16the gap library to say go from and then
  1601. 1:10:20go to. And to this you can pass two
  1602. 1:10:23different objects to define what's
  1603. 1:10:24happening with these elements. First you
  1604. 1:10:27have to target which elements you want
  1605. 1:10:29to animate. In this case I'll animate
  1606. 1:10:31the hero text class name. So make sure
  1607. 1:10:34to start with a dot. And specifically I
  1608. 1:10:37want to animate all H1 elements within
  1609. 1:10:39the hero text class name. Then we're
  1610. 1:10:42going to provide a from position. So I
  1611. 1:10:45want to animate it from these properties
  1612. 1:10:48to the following properties. Let's say
  1613. 1:10:50that I want the elements to start from a
  1614. 1:10:53bit of a higher position on the Yaxis.
  1615. 1:10:56For example, Y is 50 and opacity of
  1616. 1:10:59zero. So we can bring them to life later
  1617. 1:11:02on from zero opacity to the full
  1618. 1:11:05opacity. Then we want to animate them to
  1619. 1:11:08Y0. So we want to bring them down. We
  1620. 1:11:11want to change the opacity to one. We
  1621. 1:11:14want to stagger each one of these H1
  1622. 1:11:17elements by 0.2 seconds. So we get first
  1623. 1:11:21one 2 3. And I want to set the duration
  1624. 1:11:24of the animation to 1 second. And the
  1625. 1:11:28easing to power two dot inout. This is
  1626. 1:11:32just how this specific animation will
  1627. 1:11:34play out. So now if I save this and if I
  1628. 1:11:37increase the duration to something like
  1629. 1:11:385 seconds so you can see it better and
  1630. 1:11:40reload you should be able to see how all
  1631. 1:11:43of these three are coming one after
  1632. 1:11:45another. To make the effect even more
  1633. 1:11:47pronounced I can change the stagger to
  1634. 1:11:50about 1.5 seconds. Reload. And check
  1635. 1:11:54this out. First we have the first one.
  1636. 1:11:57Then we have the second one. And then we
  1637. 1:11:59have the third one. Of course you never
  1638. 1:12:01want to keep your users waiting. So,
  1639. 1:12:03what you can do is bring the stagger to
  1640. 1:12:050.2 and the duration to 1 second.
  1641. 1:12:08That'll end up with a nice animation
  1642. 1:12:10that looks like this. Of course, the
  1643. 1:12:11text is even larger on full screen, so
  1644. 1:12:14the animation looks even better there.
  1645. 1:12:16Looking good, right? Gap is not that
  1646. 1:12:18hard. So, let me explain what happens
  1647. 1:12:21here. The Y position moves each headline
  1648. 1:12:24from 50 to zero to give it some
  1649. 1:12:26movement. And then the opacity from 0 to
  1650. 1:12:291 fades the element in. And then a
  1651. 1:12:32stagger gives us that cascading
  1652. 1:12:35animation. Pretty cool, right? Now,
  1653. 1:12:37another thing that we need to do with
  1654. 1:12:38our hero section is to make this button
  1655. 1:12:41scroll a bit down. We cannot see the
  1656. 1:12:43scroll just yet because we haven't
  1657. 1:12:45implemented any additional sections, but
  1658. 1:12:47once we do implement it, we want it to
  1659. 1:12:49scroll very smoothly to a very precise
  1660. 1:12:52position. This is what we want to
  1661. 1:12:55achieve. I click it and we go right here
  1662. 1:12:57to the top of these statistics. and then
  1663. 1:13:00we can see the portfolio. So to
  1664. 1:13:02implement that head over into the hero
  1665. 1:13:05section and then from the hero head over
  1666. 1:13:07to the button that we created that is
  1667. 1:13:10right here within the header. There we
  1668. 1:13:13go. Now to this button we have the
  1669. 1:13:16anchor tag which is the main clickable
  1670. 1:13:18element. So let's give it an on click
  1671. 1:13:21function where we can access the click
  1672. 1:13:24event within here. The first thing we
  1673. 1:13:26want to do is prevent the default
  1674. 1:13:28behavior of the browser and that is to
  1675. 1:13:31reload the screen once we click on an
  1676. 1:13:33element. Then we want to get access to a
  1677. 1:13:36target that we want to scroll to. And
  1678. 1:13:39you can use the
  1679. 1:13:41document.getelement by ID. And whatever
  1680. 1:13:44ID you define, we can scroll to it. In
  1681. 1:13:47this case, I'll call it counter because
  1682. 1:13:49right here within these elements, we
  1683. 1:13:51want to count up some of these
  1684. 1:13:53statistics. So I'll call it counter.
  1685. 1:13:55Then we only want to scroll if we found
  1686. 1:13:58that element and if we have an ID. In
  1687. 1:14:00this case, we're only passing the ID
  1688. 1:14:03from the hero section and we won't pass
  1689. 1:14:06it later on once we use it in the
  1690. 1:14:07contact section. So the contact should
  1691. 1:14:09send the email and not scroll up. Only
  1692. 1:14:12this one should scroll up. Next, we have
  1693. 1:14:14to give it a bit of an offset, which
  1694. 1:14:16means kind of figure out to leave some
  1695. 1:14:18space at the top. So I'll say const
  1696. 1:14:21offset is equal to window.in inner
  1697. 1:14:23height and I found the number 0.15 to
  1698. 1:14:27work pretty cool. I'll show you later on
  1699. 1:14:29how it works with and without this.
  1700. 1:14:31Finally, we want to calculate how far
  1701. 1:14:34down we need to scroll. So we'll say top
  1702. 1:14:36is equal to target dot get bounding
  1703. 1:14:41clientrect dot top plus the windows
  1704. 1:14:46scroll
  1705. 1:14:47y minus the offset because we want to
  1706. 1:14:51leave some space on top of these
  1707. 1:14:53elements to make them appear nicely on
  1708. 1:14:55the screen. And finally, you can say
  1709. 1:14:57window.croll to define the top that you
  1710. 1:15:00want to scroll to and set the scroll
  1711. 1:15:03behavior to smooth. So very soon as we
  1712. 1:15:07implement those counters right here
  1713. 1:15:09below, we'll be able to click and scroll
  1714. 1:15:12down. So let's implement it. I'll head
  1715. 1:15:15back over to my hero section.
  1716. 1:15:18And below this figure and below the div
  1717. 1:15:21that's wrapping it, still within the
  1718. 1:15:23section, I'll render an animated
  1719. 1:15:25counter. But let's first create that
  1720. 1:15:27component within the components folder
  1721. 1:15:30and call it animated
  1722. 1:15:35counter.jsx. Run rafce right within it.
  1723. 1:15:38And then import it right here where we
  1724. 1:15:40were. Animated counter, a self-closing
  1725. 1:15:44component.
  1726. 1:15:45And if you do that, you should be able
  1727. 1:15:47to see it right here at the bottom. I'll
  1728. 1:15:49collapse my browser. Head into the
  1729. 1:15:51animated counter and we can implement
  1730. 1:15:53it. We can start with the JSX. I'll
  1731. 1:15:57first wrap everything in a div that has
  1732. 1:15:59an id of counter because we're going to
  1733. 1:16:02use this element to scroll to later on.
  1734. 1:16:04Next, I want to give it a class name
  1735. 1:16:07equal to
  1736. 1:16:09padding-x-lg to give them some space on
  1737. 1:16:11left and right. I also want to give it
  1738. 1:16:14on extra-large devices a margin top of
  1739. 1:16:16zero, but typically a margin top of 32.
  1740. 1:16:20Within this div, we can have yet another
  1741. 1:16:22div that'll have a class name equal to
  1742. 1:16:27margin x of auto grid of four columns.
  1743. 1:16:32So, we're going to show four different
  1744. 1:16:33cards within it. We can map over our
  1745. 1:16:36counter items. So, just start typing
  1746. 1:16:39counter items and press enter. It will
  1747. 1:16:42automatically import them from your
  1748. 1:16:44constants. Here I put different values.
  1749. 1:16:48For example, years of experience,
  1750. 1:16:50satisfied clients, completed projects,
  1751. 1:16:52and client retention rate. You can of
  1752. 1:16:55course change this if you maybe have 10
  1753. 1:16:57years of experience or less. You most
  1754. 1:16:59likely have much less, but that's
  1755. 1:17:00totally okay. Satisfied clients, you can
  1756. 1:17:03change all of these numbers or values as
  1757. 1:17:07well as the labels that you want to
  1758. 1:17:08display right here. It's all dynamic.
  1759. 1:17:11So let's map over those counter items by
  1760. 1:17:14saying that map where you get each item
  1761. 1:17:17and for each item you want to
  1762. 1:17:19automatically return a
  1763. 1:17:22div that'll look a bit like this. It'll
  1764. 1:17:26have a key equal to counter items label.
  1765. 1:17:31And you want to give it a class name
  1766. 1:17:33equal to counter dash number
  1767. 1:17:37text-white text-5 XL
  1768. 1:17:41font-bold and margin bottom of two.
  1769. 1:17:43Next, you can give it a zero number. So,
  1770. 1:17:47we're going to start all of them with a
  1771. 1:17:48zero. And then you can render the
  1772. 1:17:51item suffix. What is this suffix? Well,
  1773. 1:17:55it is either a plus for a number or a
  1774. 1:17:57percentage for the retention rate. And
  1775. 1:17:59then if you save this, you should
  1776. 1:18:01already be able to see four different
  1777. 1:18:02numbers right here. But let's actually
  1778. 1:18:05wrap this in another div. So I'll make
  1779. 1:18:08it a div like this. And I'll put it
  1780. 1:18:11right within it. We want to wrap it in a
  1781. 1:18:14card first. So this outer div will have
  1782. 1:18:17a class name of bg zinc of
  1783. 1:18:21900
  1784. 1:18:23rounded-lg padding of 10 flex flex- call
  1785. 1:18:28and a justify center. So now we can see
  1786. 1:18:31four of these different cards just
  1787. 1:18:33showcasing empty values. But before the
  1788. 1:18:36value we can actually render another div
  1789. 1:18:40that will render the item label. So, we
  1790. 1:18:43have zero years of experience, zero
  1791. 1:18:45satisfied clients, and zero completed
  1792. 1:18:47projects. Not so good. Let's give this
  1793. 1:18:50div a class name of
  1794. 1:18:55text-w. Okay, that's a bit better. But
  1795. 1:18:58still, why are we saying zero right
  1796. 1:19:01here? Well, that's because we always
  1797. 1:19:03want to start with a zero, but then we
  1798. 1:19:06want to count the elements up to the
  1799. 1:19:08value that we have chosen. either 10,
  1800. 1:19:11100 or more. Sure, what you could do is
  1801. 1:19:14immediately display it right here. So
  1802. 1:19:16that's going to be just item value. And
  1803. 1:19:19that's pretty cool already, right? But I
  1804. 1:19:22want to show you how you can animate
  1805. 1:19:24this element to go up in number to reach
  1806. 1:19:27those specific values. So let's do that.
  1807. 1:19:30Next, to implement our counter, we can
  1808. 1:19:32use a react countupub mpm package. You
  1809. 1:19:36can simply install it by saying mpm
  1810. 1:19:38install react
  1811. 1:19:42countub and then we can use it by
  1812. 1:19:44importing the countupub right at the top
  1813. 1:19:48and then using it like a countup
  1814. 1:19:50component that'll look something like
  1815. 1:19:52this. Within this div, we can implement
  1816. 1:19:55a count up and give it a start and an
  1817. 1:19:58end. If you don't provide a start, it'll
  1818. 1:20:00start from zero. And then right here as
  1819. 1:20:03the end we can implement the item dot
  1820. 1:20:06value. And you can also add a prefix or
  1821. 1:20:09a suffix to it. So the suffix can be
  1822. 1:20:13item.suffix. Let's see how that looks
  1823. 1:20:15like. I'll delete this. Save it. And if
  1824. 1:20:18I go back and reload, you can now see
  1825. 1:20:21that all of these numbers count up. Even
  1826. 1:20:24though this is 10 and this is 100, the
  1827. 1:20:26component automatically takes enough
  1828. 1:20:28time to load them all the same.
  1829. 1:20:31Perfect. This is looking amazing. And
  1830. 1:20:34let's check it out on desktop. Yep, this
  1831. 1:20:37is looking good. We have our hero
  1832. 1:20:39section which takes full height of the
  1833. 1:20:41screen. Very soon we can also add a
  1834. 1:20:44navbar right here. But for now, we have
  1835. 1:20:47this hero and then if you click on see
  1836. 1:20:49my work, it actually scrolls down to
  1837. 1:20:52these countup cards. So this is looking
  1838. 1:20:55great. And on mobile, the reason why we
  1839. 1:20:57had to put so much work into the see my
  1840. 1:21:00work button is because sometimes you
  1841. 1:21:01cannot scroll on mobile devices as
  1842. 1:21:04instead you'll be actually drag and
  1843. 1:21:06dropping our 3D model. So it was super
  1844. 1:21:08important for this see my work to
  1845. 1:21:10properly scroll down to the countup
  1846. 1:21:12section. So with that in mind, we have
  1847. 1:21:15now implemented the hero section with
  1848. 1:21:17this amazing 3D model and GEP animations
  1849. 1:21:20we have added to these H1s as well as
  1850. 1:21:22implemented a counter for some of the
  1851. 1:21:25most important metrics about our years
  1852. 1:21:27of experience, satisfied clients,
  1853. 1:21:29projects, and more. In the next lesson,
  1854. 1:21:32let's focus on developing this showcase
  1855. 1:21:35section where you can display your
  1856. 1:21:37highlighted project alongside two other
  1857. 1:21:39projects you've done
  1858. 1:21:41before. With the hero section done,
  1859. 1:21:44let's focus on the section below, the
  1860. 1:21:46showcase section, where we show three
  1861. 1:21:49real world apps you've built, one large
  1862. 1:21:52featured app on the left, and two
  1863. 1:21:54supporting projects on the right. First
  1864. 1:21:56things first, we have to create a new
  1865. 1:21:58section. So within the sections folder,
  1866. 1:22:00create a new file and call it showcase
  1867. 1:22:06section.jsx, run
  1868. 1:22:08rafce and imported right within the app
  1869. 1:22:11below the hero showcase section right
  1870. 1:22:15here. If you do that, you'll be able to
  1871. 1:22:17see a showcase section text right below.
  1872. 1:22:20So let's get started with the layout.
  1873. 1:22:22First, I'll wrap everything with a div
  1874. 1:22:24and I'll give it an ID equal to work so
  1875. 1:22:27that we can use the navigation bar links
  1876. 1:22:29later on to scroll to that section. Then
  1877. 1:22:31I'll give it a class name of app
  1878. 1:22:34dashcase. And this will nicely center it
  1879. 1:22:37on the screen. Right within it, I'll
  1880. 1:22:39create another div. And this one will
  1881. 1:22:42have a class name equal to w- full. So
  1882. 1:22:45it takes the full width of the screen.
  1883. 1:22:47And then within it, I'll have another
  1884. 1:22:49div with a class name equal to showcase
  1885. 1:22:54layout. This one we'll make it flex row
  1886. 1:22:57on extra-l large devices. So we can fit
  1887. 1:22:59one on the left and two on the right.
  1888. 1:23:00And flex call so all of them show one
  1889. 1:23:03below another on mobile devices. So now
  1890. 1:23:05we can split this into two sides. We're
  1891. 1:23:08going to have the left side right here
  1892. 1:23:10on the top and then after that we'll
  1893. 1:23:12have the right side. So on the left side
  1894. 1:23:15let's create a new div. This div will
  1895. 1:23:17have a class name equal to first project
  1896. 1:23:22wrapper. And within it, I'll create
  1897. 1:23:24another div with a class name of
  1898. 1:23:28image-wrapper. And within it, I'll
  1899. 1:23:30render a self-closing image with a
  1900. 1:23:32source of forward/
  1901. 1:23:36images/roject
  1902. 1:23:381.png. And I'll give it an al tag of
  1903. 1:23:41ride because if you take a look at my
  1904. 1:23:44images right here within the public
  1905. 1:23:46folder images and then we have our
  1906. 1:23:49project one you'll see that this is ride
  1907. 1:23:52a react native Uberlike application
  1908. 1:23:55which if you want to learn how to build
  1909. 1:23:57well you can do it for free on YouTube.
  1910. 1:23:59If you wanted to put the image or the
  1911. 1:24:01thumbnail of your project, which you
  1912. 1:24:03most likely want to do, unless you build
  1913. 1:24:06this one, of course, then you'll have to
  1914. 1:24:07upload your own image to the images
  1915. 1:24:09folder and just refer to it right here.
  1916. 1:24:12And then you'll be able to see it right
  1917. 1:24:13at the bottom. Within this div
  1918. 1:24:15containing the image, I'll create
  1919. 1:24:17another div with a class name equal to
  1920. 1:24:21text-content. And within it, I'll
  1921. 1:24:24display an
  1922. 1:24:25H2 that'll say
  1923. 1:24:27ondemand rides made simple with a
  1924. 1:24:32powerful
  1925. 1:24:34userfriendly app called Ride. Of course,
  1926. 1:24:38you can enter the title of your
  1927. 1:24:40application right here. Then we can have
  1928. 1:24:42another description paragraph right
  1929. 1:24:44below with a class name of
  1930. 1:24:49text-white-50 and on medium devices
  1931. 1:24:51text- XL. And here I'll copy what I have
  1932. 1:24:54in the already deployed website, an app
  1933. 1:24:57built with React Native, Expo, Tailwind
  1934. 1:24:59CSS for fast, user-friendly
  1935. 1:25:02experiences. Of course, I invite you to
  1936. 1:25:05talk a bit more about the technologies
  1937. 1:25:06you used here or what obstacles you
  1938. 1:25:09encountered while developing it. And now
  1939. 1:25:11we can head over below this div for the
  1940. 1:25:14left side and focus on the right side.
  1941. 1:25:17There we can have one div which will
  1942. 1:25:19wrap those two additional projects with
  1943. 1:25:21a class name of project list wrapper and
  1944. 1:25:27overflow-hidden. Within it we can have a
  1945. 1:25:29div for our first project. So let's give
  1946. 1:25:32it a class name of project. And within
  1947. 1:25:34it we can render a div within which we
  1948. 1:25:37can render that project's image. So
  1949. 1:25:40let's give it a source of images project
  1950. 1:25:442.png with an alt tag of library
  1951. 1:25:47management platform. If you save it,
  1952. 1:25:50you'll see one image right here. But we
  1953. 1:25:53can position it a bit better by styling
  1954. 1:25:55this div that's wrapping it with a class
  1955. 1:25:57name of
  1956. 1:26:00image-wrapper bg of hash
  1957. 1:26:05ffb just like this. So we nicely
  1958. 1:26:07position it within the screen. Right
  1959. 1:26:09below this div containing the image, we
  1960. 1:26:11can also render an H2 where we can
  1961. 1:26:13simply say library management platform.
  1962. 1:26:16In this case, we don't need to give it a
  1963. 1:26:18description because this is a secondary
  1964. 1:26:19project. So let's go ahead and copy this
  1965. 1:26:22entire project div. So for me, that's
  1966. 1:26:24this red one right here. And I'll paste
  1967. 1:26:27it one more time just below this time
  1968. 1:26:30for our second project. So I'll change
  1969. 1:26:32the background color a bit so they're
  1970. 1:26:34not the same to
  1971. 1:26:37FF7 EB. There we go. So just a bit of a
  1972. 1:26:40different color. Image will point to
  1973. 1:26:42project 3. And the AL tag will say YC
  1974. 1:26:47directory. This is yet another of our
  1975. 1:26:49YouTube
  1976. 1:26:50projects. YC directory which is a
  1977. 1:26:54startup
  1978. 1:26:56showcase app. Of course, I invite you to
  1979. 1:27:00put your own projects right here. And
  1980. 1:27:02now is the time that we add some
  1981. 1:27:04animations to this screen. It doesn't
  1982. 1:27:07look that exciting on mobile, but on
  1983. 1:27:09desktop, we do have a big project in the
  1984. 1:27:12left and two smaller ones on the right.
  1985. 1:27:14But if you compare it with the final
  1986. 1:27:15project, you'll notice that there they
  1987. 1:27:18nicely appear one after another. Whereas
  1988. 1:27:21on our current application, they just
  1989. 1:27:23show up and that's it. So let's animate
  1990. 1:27:26them one by one. To do that, you'll want
  1991. 1:27:29to give each one of these project divs
  1992. 1:27:31its own reference. So right at the top,
  1993. 1:27:34let's create a new const section ref is
  1994. 1:27:38equal to use ref coming from react. And
  1995. 1:27:42at the start, set it to be equal to
  1996. 1:27:44null. We need one for the section. And
  1997. 1:27:46then we need one for each one of the
  1998. 1:27:47projects. So I'll say con project one
  1999. 1:27:50ref is equal to use
  2000. 1:27:53ref at the start set to null again and
  2001. 1:27:57then I'll duplicate it two more times
  2002. 1:27:59for the project 2 and project 3. Now we
  2003. 1:28:03can give a ref to the main section. So
  2004. 1:28:05I'll actually rename this to an HTML 5
  2005. 1:28:08semantic section tag with a ref of
  2006. 1:28:12section ref. And then I'll also give it
  2007. 1:28:14to each one of these projects. The first
  2008. 1:28:16one being this first project wrapper.
  2009. 1:28:19I'll give it a ref equal to project one
  2010. 1:28:23ref. And then I'll head over to this
  2011. 1:28:26second project and I'll give it a ref of
  2012. 1:28:28project 2 ref. And finally for the third
  2013. 1:28:32one I'll give it a ref of project 3 ref.
  2014. 1:28:36So now all of these different divs or
  2015. 1:28:39projects have different references
  2016. 1:28:41attached to them. So now we can apply
  2017. 1:28:44some animations on them. Let me show you
  2018. 1:28:46how to do that using GSAP. First things
  2019. 1:28:48first, we will import GSAP within curly
  2020. 1:28:52braces from GSAP. And the one thing that
  2021. 1:28:55you want to do is say
  2022. 1:28:58GSAP.register plugin. And you'll pass in
  2023. 1:29:01a scroll trigger which you'll need to
  2024. 1:29:04import from Gap. So say import in curly
  2025. 1:29:07braces scroll trigger coming from gap
  2026. 1:29:11scroll
  2027. 1:29:12trigger and finally we'll also need to
  2028. 1:29:15import the use gap hook which we used
  2029. 1:29:17before which is coming from at gap
  2030. 1:29:20react. You need to register this plugin
  2031. 1:29:24because otherwise gap won't know that
  2032. 1:29:26you want to apply some scroll related
  2033. 1:29:28features. You already know how to start
  2034. 1:29:30applying gap animations. You do it using
  2035. 1:29:33the use gap hook. So let's say use gap
  2036. 1:29:37right here. And you have to give it a
  2037. 1:29:39callback function with a dependency
  2038. 1:29:41array right here at the bottom. It works
  2039. 1:29:44similarly to React use
  2040. 1:29:46effect. This means that everything in
  2041. 1:29:49this block of code runs only once when
  2042. 1:29:51the component mounts. So there's one
  2043. 1:29:53thing that I want to do first and that
  2044. 1:29:56is fade in the entire section that shows
  2045. 1:29:59these different projects. I can do that
  2046. 1:30:02by using the section
  2047. 1:30:05ref.curren and I want to animate it from
  2048. 1:30:07and to something. So I'll actually have
  2049. 1:30:10to say gap from to and then the first
  2050. 1:30:14parameter you pass to it is the
  2051. 1:30:16reference to a div that you want to
  2052. 1:30:18animate. Then you pass from which
  2053. 1:30:21animations you want to go. So I want to
  2054. 1:30:23go from opacity of zero. And then you
  2055. 1:30:26for the third parameter pass where you
  2056. 1:30:28want to go. You want to go to opacity of
  2057. 1:30:31one throughout the duration of 1.5
  2058. 1:30:36seconds. And I'll actually put this into
  2059. 1:30:38multiple lines so it's easier to see
  2060. 1:30:40what's
  2061. 1:30:41happening. So gap from two apply to this
  2062. 1:30:45div opacity from zero to 1 throughout
  2063. 1:30:49the duration of 1.5 seconds. If you do
  2064. 1:30:52that and reload, you'll notice that now
  2065. 1:30:55it fades in pretty nicely. And now we
  2066. 1:30:57want to animate each project on scroll
  2067. 1:31:00one by one. We can do that by gathering
  2068. 1:31:03them into an array and then loop through
  2069. 1:31:06each one and create a scroll animation.
  2070. 1:31:09So right at the top I'll say const
  2071. 1:31:11projects and create an array of those
  2072. 1:31:14projects. Then to it I want to add a
  2073. 1:31:18project one
  2074. 1:31:20ref.curren, project two ref.curren
  2075. 1:31:24current and finally project 3
  2076. 1:31:27ref.curren. So we have stored all of
  2077. 1:31:29these references in an
  2078. 1:31:31array. Then we want to say
  2079. 1:31:35cards. So for each card and take its
  2080. 1:31:38card
  2081. 1:31:39index open up a new function
  2082. 1:31:42block and then run the gap from to
  2083. 1:31:48function. What do you want to animate?
  2084. 1:31:50Well, I want to animate a card. Which
  2085. 1:31:52card? Well, the one that we're going
  2086. 1:31:54over for that specific round of the for
  2087. 1:31:57each loop. And what do I want to do to
  2088. 1:31:59it? I want to start it at
  2089. 1:32:02Y50 and opacity of zero, which means
  2090. 1:32:05that it starts as
  2091. 1:32:07invisible. And then what I want to do to
  2092. 1:32:10it is I want to change the Y to zero,
  2093. 1:32:13which means that we're going to move it
  2094. 1:32:14back to its normal position and make it
  2095. 1:32:17fully visible by changing the opacity to
  2096. 1:32:19one throughout a duration of 1 second.
  2097. 1:32:22And I also want to add a delay to
  2098. 1:32:25stagger when they load. So I'll say 0.3
  2099. 1:32:29seconds and then take the index plus
  2100. 1:32:32one. That's because the indices start at
  2101. 1:32:35zero. So instead of having 0 to 1, we'll
  2102. 1:32:37have 1 * 0.3. So the delay for the first
  2103. 1:32:40one will be 0.3. Then for the next one,
  2104. 1:32:43it'll increase to 0.6. And then it'll
  2105. 1:32:46increase one more time to 0.9. And
  2106. 1:32:49finally, to know when we want to apply
  2107. 1:32:50this scroll animation, we have to have a
  2108. 1:32:53scroll trigger. Here you can say when
  2109. 1:32:56you reach which portion of the screen,
  2110. 1:32:58when do they want to trigger it? And we
  2111. 1:33:00can say the trigger is actually the
  2112. 1:33:02card. So animate when the card enters
  2113. 1:33:05the viewport. And then you can decide
  2114. 1:33:07when exactly you want to start it. For
  2115. 1:33:09example, start when the top of the card
  2116. 1:33:12is near the bottom of the screen. So
  2117. 1:33:15about 100 pixels from the bottom. And
  2118. 1:33:18right here, I should have said projects.
  2119. 1:33:20So we're mapping over each one of these
  2120. 1:33:22project cards. Okay. So now if you
  2121. 1:33:25reload and scroll down, well, you won't
  2122. 1:33:29be able to see the animation just yet.
  2123. 1:33:30You'll just be able to see this one
  2124. 1:33:32where it goes from opacity zero to
  2125. 1:33:34opacity 1. So why cannot we see this
  2126. 1:33:37animation? Is it something with the for
  2127. 1:33:40each loop? Well, not really. It's just
  2128. 1:33:43that you have to use Gap from within the
  2129. 1:33:46use GSAB hook. So, simply select this
  2130. 1:33:49and bring it down into the use GUB hook
  2131. 1:33:53right
  2132. 1:33:54here. There we go. And you can also
  2133. 1:33:56bring these projects here. So, now we're
  2134. 1:33:59saying use Gap and then we're applying
  2135. 1:34:02the GSAP animation. So, if I now save
  2136. 1:34:05this, scroll up to the top, reload, and
  2137. 1:34:09then scroll down, you can now see how
  2138. 1:34:11each one of these nicely appears as you
  2139. 1:34:14scroll. And of course, let's not forget
  2140. 1:34:16that this looks even better on desktop
  2141. 1:34:18devices. So, as you scroll, we get all
  2142. 1:34:20of them appear nicely one by one. Great.
  2143. 1:34:24And that brings us to the end of the
  2144. 1:34:26showcase section. Now, we could focus on
  2145. 1:34:29the next section, but now that we have
  2146. 1:34:31the hero and one more section, what we
  2147. 1:34:34can focus on instead is implementing the
  2148. 1:34:36navigation bar because now we have two
  2149. 1:34:38different things that we can scroll from
  2150. 1:34:40and to. So, let's do that.
  2151. 1:34:43Next, let's get started with developing
  2152. 1:34:46our navbar. It should look something
  2153. 1:34:48like this on desktop where if you scroll
  2154. 1:34:51down, you can always get back to the top
  2155. 1:34:53of it by clicking your name. And then
  2156. 1:34:55here it allows you to smoothly scroll to
  2157. 1:34:58different parts of your portfolio. And
  2158. 1:35:01then on top we have contact which brings
  2159. 1:35:03us all the way to the bottom to this
  2160. 1:35:06great 3D model that we'll implement
  2161. 1:35:08soon. And then if you check it out on
  2162. 1:35:10mobile it'll be very simple. We don't
  2163. 1:35:13need the viewer to necessarily see every
  2164. 1:35:15single thing. If they want it they're
  2165. 1:35:17going to scroll but then at the moment
  2166. 1:35:18where they want to contact you they can
  2167. 1:35:20just go here and do that immediately. So
  2168. 1:35:23let's focus on implementing the knobbar.
  2169. 1:35:26We can start by creating a new component
  2170. 1:35:29right within source and components and
  2171. 1:35:32create it right here by creating a new
  2172. 1:35:37navbar.jsx file. Run rafce and just
  2173. 1:35:41import it over in your
  2174. 1:35:43app.jsx above the hero section. So
  2175. 1:35:46that'll be the navbar like this. If you
  2176. 1:35:49do that and go back to your website, you
  2177. 1:35:51should be able to see it right here on
  2178. 1:35:53the top and then we can implement it.
  2179. 1:35:56Just head over into it, turn it into an
  2180. 1:35:58HTML 5 semantic header tag and give it a
  2181. 1:36:02class name equal to navbar and within it
  2182. 1:36:06create a div that'll have a class name
  2183. 1:36:09of
  2184. 1:36:10inner and within it render an anchor tag
  2185. 1:36:13that'll have a class name of logo and an
  2186. 1:36:17href of hash hero pointing to your hero
  2187. 1:36:21section when clicked. And there you can
  2188. 1:36:24type your name. I'll type Adrian
  2189. 1:36:27JSM. Of course, you can put your first
  2190. 1:36:29and last name right there. There we go.
  2191. 1:36:33Pretty cool. Right now, right below this
  2192. 1:36:35anchor tag, we can render nav for the
  2193. 1:36:38rest of the navigation items. And here,
  2194. 1:36:41I'll give it a class name of desktop.
  2195. 1:36:44So, we know that whatever is within this
  2196. 1:36:46nav only appears on desktop. But then
  2197. 1:36:49after the nav, I'll render another
  2198. 1:36:52anchor tag that'll have an href pointing
  2199. 1:36:56to hash contact with a class name of
  2200. 1:37:01contact-bn and group. And within it, we
  2201. 1:37:05can render a div with a class name of
  2202. 1:37:08inner. And within it, we can render a
  2203. 1:37:11span that'll simply say contact me.
  2204. 1:37:15There we go. So now we have a contact as
  2205. 1:37:17well as AdrianJSM right here at the top.
  2206. 1:37:21Now the question is what do we show
  2207. 1:37:22within the nav? Well, for that we'll
  2208. 1:37:25have to head over to the desktop version
  2209. 1:37:26of the application. So I'll expand it a
  2210. 1:37:29bit and within it let's render a ul, an
  2211. 1:37:33unordered list. Within the unordered
  2212. 1:37:36list, we want to map over all of our
  2213. 1:37:38link items. Sure, you could create an
  2214. 1:37:40array of objects where you have a title
  2215. 1:37:43for each one and then you also have a
  2216. 1:37:45URL that you want to point to, but it's
  2217. 1:37:48always better to declare those links in
  2218. 1:37:51your constants in case they change. You
  2219. 1:37:53don't have to change them here. So,
  2220. 1:37:55simply say nav links and import it over
  2221. 1:37:58from constants index.js. If you want to
  2222. 1:38:01change those links, you can, but these
  2223. 1:38:04are the sections that our portfolio will
  2224. 1:38:06have. work, experience, skills, and
  2225. 1:38:09testimonials. Sections that every single
  2226. 1:38:11portfolio needs to have. So once you're
  2227. 1:38:14in there, just map over them, dstructure
  2228. 1:38:17the link and the name that we get from
  2229. 1:38:20each one of these links, and then for
  2230. 1:38:22each one, automatically return a list
  2231. 1:38:25item, an LI. And since we're mapping
  2232. 1:38:28over it, we also have to provide it a
  2233. 1:38:30key, which can be a name since it's
  2234. 1:38:33unique with a class name of group. And
  2235. 1:38:37within each one, we can render an anchor
  2236. 1:38:39tag that'll point to the link. So this
  2237. 1:38:43is a dynamic link property different for
  2238. 1:38:45each one of these links. Within it, we
  2239. 1:38:48can render a span element that'll render
  2240. 1:38:51a name. And within it, we can also
  2241. 1:38:53render another self-closing span that
  2242. 1:38:56will have a class name of
  2243. 1:38:59underline, allowing us to have that very
  2244. 1:39:01cool visual effect. So if you save it,
  2245. 1:39:04you should be able to see work,
  2246. 1:39:06experience, skills, and testimonials
  2247. 1:39:08with this cool underline. And if you
  2248. 1:39:11click on work, since that is the first
  2249. 1:39:13section we have implemented, you can
  2250. 1:39:14immediately see it right here alongside
  2251. 1:39:16its nice
  2252. 1:39:18animation. Let's try it one more time. I
  2253. 1:39:20can head up and I can see my work both
  2254. 1:39:23here and here and it nicely animates.
  2255. 1:39:27Pretty cool, right? Now, check this out.
  2256. 1:39:30If we scroll down, you'll notice that
  2257. 1:39:32our navbar will kind of be hidden by
  2258. 1:39:34some of the other more colorful elements
  2259. 1:39:36that we have on the screen. That means
  2260. 1:39:39that we want to actually give it some
  2261. 1:39:41kind of a background so that it's
  2262. 1:39:42visible even on top of these elements.
  2263. 1:39:45For that, we have to keep track of the
  2264. 1:39:47state of the navbar. If we're here, just
  2265. 1:39:50leave it like this. It has enough
  2266. 1:39:52contrast with the background. But as
  2267. 1:39:54soon as we scroll down, we'll then apply
  2268. 1:39:57some kind of a background. To do that,
  2269. 1:39:59we have to figure out when have we
  2270. 1:40:00scrolled and when have we not. So right
  2271. 1:40:04here at the top of the navbar, I'll say
  2272. 1:40:06const and I'll define a new use state
  2273. 1:40:10called scrolled and set scrolled. At the
  2274. 1:40:13start, it'll be equal to the use state.
  2275. 1:40:16That'll simply say false. And this use
  2276. 1:40:19state of course is coming from React.
  2277. 1:40:23Then I'll open up a new use effect
  2278. 1:40:25coming from React that'll happen only at
  2279. 1:40:28the start or when the component gets
  2280. 1:40:30mounted. So I need to define the
  2281. 1:40:33dependency array as an empty array. And
  2282. 1:40:36within there we want to figure out how
  2283. 1:40:38do we handle the scroll. So I'll create
  2284. 1:40:41a handle scroll function which will
  2285. 1:40:43check whether the user has scrolled at
  2286. 1:40:46least 10 pixels. So I'll say const is
  2287. 1:40:49scrolled is equal to
  2288. 1:40:53windowscrolly is greater than 10. If
  2289. 1:40:56that is the case I'll set scrolled to
  2290. 1:40:58true because it means that the user has
  2291. 1:41:00scrolled. And now I want to add this
  2292. 1:41:03event listener to the window. It'll be a
  2293. 1:41:05scroll event listener and it'll call the
  2294. 1:41:08handle scroll function. And I also want
  2295. 1:41:11to unmount it to clean it up. So I'll
  2296. 1:41:14say return callback function
  2297. 1:41:17window.ventlist listener scroll and
  2298. 1:41:21handle scroll. So this way we're still
  2299. 1:41:24keeping all of the optimization in React
  2300. 1:41:26by removing or cleaning up the event
  2301. 1:41:28listener when the component is
  2302. 1:41:30unmounted. So now that we have access to
  2303. 1:41:32this scroll state, let's put it to use.
  2304. 1:41:35See this navbar? We want to apply
  2305. 1:41:37different classes to it depending on
  2306. 1:41:39whether the user has scrolled or not. So
  2307. 1:41:42I'll make this class name dynamic and a
  2308. 1:41:44dynamic template string for that matter
  2309. 1:41:47it'll always have the class name of
  2310. 1:41:48navbar but if scrolled is true in that
  2311. 1:41:52case I'll also give it a class name of
  2312. 1:41:54scrolled else I'll give it a class name
  2313. 1:41:56of not scrolled. So if you check out
  2314. 1:41:58both of these you can see that scrolled
  2315. 1:42:00simply applies a top zero and a
  2316. 1:42:03background black and then not scrolled
  2317. 1:42:05applies a background transparent. That's
  2318. 1:42:08it. That's the only difference. So now
  2319. 1:42:10if you scroll down right here, you'll
  2320. 1:42:13notice that now it has a dark
  2321. 1:42:15background. So it's still fully visible
  2322. 1:42:18even when appearing over all of these
  2323. 1:42:19elements. And then if I go up, it simply
  2324. 1:42:22disappears. Makes sense, right? And with
  2325. 1:42:24that in mind, your navbar is now fully
  2326. 1:42:27completed. It scrolls to work and to
  2327. 1:42:30home, but later on it'll also scroll to
  2328. 1:42:32all of these additional sections which
  2329. 1:42:35we just about to implement.
  2330. 1:42:37So let's start with the next one in the
  2331. 1:42:39list and that one is the logo showcase
  2332. 1:42:42that scrolls through the logos of
  2333. 1:42:44different companies that you have worked
  2334. 1:42:46with in the past. So let's do that
  2335. 1:42:50next. Let's start implementing the logo
  2336. 1:42:54carousel. This is all about social
  2337. 1:42:57proof. You want to show off client logos
  2338. 1:42:59in a smooth continuous scrolling. This
  2339. 1:43:02is called a marquee, I think, just like
  2340. 1:43:05the top tier agency sites do. So, let's
  2341. 1:43:07create a new component just for it. I'll
  2342. 1:43:10head over into the components folder and
  2343. 1:43:12create a new file called logo
  2344. 1:43:17section.jsx within it. You know the
  2345. 1:43:19drill. Run rafce and then import it
  2346. 1:43:23right here. Logo section as a
  2347. 1:43:26self-closing component right below the
  2348. 1:43:30showcase section. Once you do that, you
  2349. 1:43:32should be able to see an empty piece of
  2350. 1:43:33text. So, let's head over into it and
  2351. 1:43:37instead of this empty div, create a div
  2352. 1:43:40that has a class name of on medium
  2353. 1:43:43devices, margin Y of 20. So that's 20 on
  2354. 1:43:47top and bottom, but typically margin Y
  2355. 1:43:49of 10. So only on medium devices do we
  2356. 1:43:52want to have that larger margin Y. And
  2357. 1:43:55then also give it a position of
  2358. 1:43:57relative. right within it. Create two
  2359. 1:44:00separate self-closing divs. Both of
  2360. 1:44:02which will have a class name equal to
  2361. 1:44:05gradient edge and then duplicate it.
  2362. 1:44:09You'll see very soon what that does, but
  2363. 1:44:11essentially you can see how these logos
  2364. 1:44:13disappear on the left and then they
  2365. 1:44:15start appearing from the right as it
  2366. 1:44:17moves. So right below create a new div
  2367. 1:44:20that'll have a class name equal to
  2368. 1:44:24marquee and give it a height of about
  2369. 1:44:2752. Then right within it create another
  2370. 1:44:30div that'll have a class name of marquee
  2371. 1:44:34box with a medium devices of gap 12 in
  2372. 1:44:38between the elements and typically a gap
  2373. 1:44:40of five should do the trick. This
  2374. 1:44:42marquee is the visible area that you see
  2375. 1:44:44on the screen. But the marquee box will
  2376. 1:44:47actually have twice the width and it'll
  2377. 1:44:50animate indefinitely to the left. This
  2378. 1:44:52is how you can apply animations. So
  2379. 1:44:54let's research them a bit deeper. You
  2380. 1:44:57can see that this marquee takes 100
  2381. 1:44:59dynamic viewport width and we hide it.
  2382. 1:45:02Okay, so this is what you can see on the
  2383. 1:45:04screen. But this marquee box will
  2384. 1:45:06actually have 200% width and then an
  2385. 1:45:10overflow hidden. So, we're animating it
  2386. 1:45:13outside of the screen. And we're
  2387. 1:45:14applying a simple CSS animation of
  2388. 1:45:17Marquee 60 seconds linear infinite.
  2389. 1:45:21Believe it or not, that's all it takes.
  2390. 1:45:23You can do a lot of stuff built right
  2391. 1:45:25into CSS. So, the only thing remaining
  2392. 1:45:27for us to do is to add the logos. We're
  2393. 1:45:30going to loop through all of the logos
  2394. 1:45:32twice so it feels infinite. First, we
  2395. 1:45:34need to import the list of the logos.
  2396. 1:45:37So, I'll say logo icons list coming from
  2397. 1:45:40constants. And as you can see, I put
  2398. 1:45:43about 11 logos right here. But feel free
  2399. 1:45:46to remove them or feel free to add the
  2400. 1:45:48logos of the companies that you worked
  2401. 1:45:50for or that you collaborated with. And
  2402. 1:45:53if you didn't work with any major
  2403. 1:45:54companies that have known or
  2404. 1:45:57good-looking logos, well, then you might
  2405. 1:45:59just skip this section altogether. if
  2406. 1:46:01you want to. The only thing you have to
  2407. 1:46:03do is comment it out from here and then
  2408. 1:46:05move over to the next section. With that
  2409. 1:46:08said, I will map over it, get an icon
  2410. 1:46:11for each one, and then simply display a
  2411. 1:46:14new component for each one of these. A
  2412. 1:46:17component called logo icon. So, let me
  2413. 1:46:20define it right here since this is the
  2414. 1:46:22only place where it'll be used. Logo
  2415. 1:46:25icon. It is a self-closing component
  2416. 1:46:28that simply accepts an icon and it
  2417. 1:46:32returns a
  2418. 1:46:33div with a class name of flex-ash none
  2419. 1:46:38flex- center and a marquee
  2420. 1:46:41item and it'll render an image of a
  2421. 1:46:46source icon img path as well as an al
  2422. 1:46:50tag of
  2423. 1:46:52icon.name. So now we can loop over or
  2424. 1:46:55map over those logo icons like this. We
  2425. 1:47:00can give it a key equal to
  2426. 1:47:03icon.name and then we can pass over the
  2427. 1:47:05entire icon equal to icon to it. So now
  2428. 1:47:08if you save this, you'll be able to see
  2429. 1:47:11the logos. But what we have to do is
  2430. 1:47:13actually duplicate it one more time. And
  2431. 1:47:16we're doing this because this trick
  2432. 1:47:18creates a duplicate stream of logos. So
  2433. 1:47:21when the first one sets to scroll out,
  2434. 1:47:23the second one continues seamlessly.
  2435. 1:47:26Hopefully it makes sense. But yeah,
  2436. 1:47:28believe it or not, that is it. The magic
  2437. 1:47:30here lies in this animation CSS property
  2438. 1:47:34where you apply a specific type of the
  2439. 1:47:36animation. Choose the duration, choose
  2440. 1:47:39whether it's linear or not, and then
  2441. 1:47:41make it infinite and then that'll scroll
  2442. 1:47:43the images from left to right. Pretty
  2443. 1:47:45cool, right? Once again, I'm totally
  2444. 1:47:47aware that you might not have hundreds
  2445. 1:47:49of companies you worked for in the past.
  2446. 1:47:51So, if that is the case, you can totally
  2447. 1:47:53just comment this section out and focus
  2448. 1:47:55on the next one to come. Because the
  2449. 1:47:57next ones are cards that feature your
  2450. 1:48:01strengths. To start implementing the
  2451. 1:48:04featured cards, let's go ahead and
  2452. 1:48:06create a new section within the sections
  2453. 1:48:08folder. I'll call it feature
  2454. 1:48:13cards.jsx. run rafce and simply import
  2455. 1:48:17it within the app feature cards. Now
  2456. 1:48:22that I look at it, the logo section is
  2457. 1:48:24also technically a section and not a
  2458. 1:48:26component. So, let me move it right here
  2459. 1:48:28into the sections, but make sure to fix
  2460. 1:48:31up these imports so that it's coming
  2461. 1:48:33from sections instead of from
  2462. 1:48:37components. Great. With that said, move
  2463. 1:48:40over into the feature cards and let's
  2464. 1:48:42start implementing them.
  2465. 1:48:44The goal of these cards is to highlight
  2466. 1:48:47your top strengths as a developer.
  2467. 1:48:49Things like the quality, communication,
  2468. 1:48:51and timely delivery of your projects.
  2469. 1:48:54Each card is a compact summary of one of
  2470. 1:48:58your core abilities using an icon, a
  2471. 1:49:00title, and a description. So first let's
  2472. 1:49:03create a layout by creating a div
  2473. 1:49:07that'll have a class name of w full
  2474. 1:49:12padding x of lg and within it create
  2475. 1:49:15another div that'll have a class name of
  2476. 1:49:19margin x auto and let's split it into a
  2477. 1:49:22grid that has three columns. Now you
  2478. 1:49:25want to map over your abilities coming
  2479. 1:49:28from constants index jsx. These are some
  2480. 1:49:32of the abilities that Chad GPT thinks
  2481. 1:49:34are important, but I want you to go
  2482. 1:49:37ahead and change all of this text right
  2483. 1:49:39here because you are a special
  2484. 1:49:41individual and you have your own
  2485. 1:49:43strengths. So change the titles,
  2486. 1:49:45descriptions, and if needed, image paths
  2487. 1:49:48as well to showcase what you are capable
  2488. 1:49:51of. Then we want to map over all of
  2489. 1:49:54these different strengths. And for each
  2490. 1:49:56one, we can dstructure the image path,
  2491. 1:50:00the title, and the description. And then
  2492. 1:50:03we can just map over them and
  2493. 1:50:05automatically return them. For each one,
  2494. 1:50:08I'll return a div. And for now, I'll
  2495. 1:50:11make that div simply render the H3
  2496. 1:50:15that'll say title. If you do that and go
  2497. 1:50:19back to our current application, you'll
  2498. 1:50:21see quality focus, reliable
  2499. 1:50:23communication, and on-time
  2500. 1:50:25delivery. Let's style it a bit different
  2501. 1:50:27to make it a card. First, I'll give it a
  2502. 1:50:30key since this is an element we're
  2503. 1:50:32mapping over. I'll make it a title since
  2504. 1:50:34the title is unique. And I'll give it a
  2505. 1:50:37class name of card-border.
  2506. 1:50:42I'll also give it a rounded dash XL
  2507. 1:50:46which will make the corners rounded. A
  2508. 1:50:48padding of eight to give it some
  2509. 1:50:51space. Also
  2510. 1:50:53flex flex- call and a gap of four in
  2511. 1:50:57between the elements within the cards.
  2512. 1:50:59Next, I'll create an inner div right
  2513. 1:51:02here that'll have a class name equal to
  2514. 1:51:06size of 14 flex items center justify-
  2515. 1:51:12center and rounded- full. This is for
  2516. 1:51:16the icon we're going to display. So, let
  2517. 1:51:18me render a self-closing image tag with
  2518. 1:51:21a source of img path and an al tag of
  2519. 1:51:25title.
  2520. 1:51:27If you save this, you should be able to
  2521. 1:51:28see this icon appear, which already
  2522. 1:51:31brings so much more life into it. And
  2523. 1:51:34below it, we have this H3 to which we
  2524. 1:51:37can give a class name of
  2525. 1:51:39text-wide
  2526. 1:51:42text-2XL font- semibold and margin top
  2527. 1:51:45of
  2528. 1:51:46two. There we go. Looking good. And
  2529. 1:51:49below it, we can render a P tag with a
  2530. 1:51:52class name of
  2531. 1:51:55text-white-50 and
  2532. 1:51:58text-LG. And it can render the
  2533. 1:52:01description. There we go. This is
  2534. 1:52:04already looking so much better. One
  2535. 1:52:06thing that I noticed is that right here,
  2536. 1:52:08I misspelled padding. So, this was
  2537. 1:52:10supposed to say padding
  2538. 1:52:13XLG. And that'll provide some padding on
  2539. 1:52:15left and right. And to give it some more
  2540. 1:52:18vertical space to breathe, we need to
  2541. 1:52:20rename this to grid three calls as in
  2542. 1:52:23columns plural because this will apply
  2543. 1:52:26this grid column layout which will
  2544. 1:52:28nicely space them out on all different
  2545. 1:52:30types of devices. There we go. This is
  2546. 1:52:32looking better. Looks great on mobile
  2547. 1:52:35and nicely complements this logo
  2548. 1:52:37section. And if you check it out on
  2549. 1:52:39desktop, it looks great too. Perfect. Oh
  2550. 1:52:44yeah, I don't think I've seen the logo
  2551. 1:52:45section on desktop. It looks even better
  2552. 1:52:48because now you should get the idea of
  2553. 1:52:50why we're using double the icons. So
  2554. 1:52:52once some scroll to the left side of the
  2555. 1:52:54screen, new ones appear on the right
  2556. 1:52:56side. Perfect. This section is done as
  2557. 1:53:00well. So now we can showcase not only
  2558. 1:53:02the companies you worked for, but also
  2559. 1:53:05your top features. But what matters even
  2560. 1:53:08more than features is your experience.
  2561. 1:53:11So, let's develop one of the most
  2562. 1:53:13exciting sections of this portfolio
  2563. 1:53:15next. Your work experience timeline. It
  2564. 1:53:20has a couple of interesting elements
  2565. 1:53:22such as this card right here that has
  2566. 1:53:24this cool hover effect or this timeline
  2567. 1:53:28that expands as you scroll down and
  2568. 1:53:30changes colors depending on the logo of
  2569. 1:53:33the company that you worked for. Pretty
  2570. 1:53:35cool stuff, right? So, let's do that
  2571. 1:53:38next.
  2572. 1:53:41professional work experience. Or maybe
  2573. 1:53:43it's not work experience for you, but
  2574. 1:53:45rather you're showcasing the timeline of
  2575. 1:53:47how you build different projects. Either
  2576. 1:53:50way, this section will tell your career
  2577. 1:53:53journey in a visually structured
  2578. 1:53:55timeline, blending the clean layout with
  2579. 1:53:58these animated transitions. On the left
  2580. 1:54:01side, you'll have testimonial style
  2581. 1:54:03experience cards. In the middle, we'll
  2582. 1:54:05have a timeline, and on the right side,
  2583. 1:54:08you'll have all of your job
  2584. 1:54:09responsibilities. So, let's start with
  2585. 1:54:11creating this section. I'll head over to
  2586. 1:54:13the sections folder and create a new
  2587. 1:54:16experience
  2588. 1:54:20section.jsx. I'll run rafce and I'll
  2589. 1:54:23simply import it over below the feature
  2590. 1:54:26cards. There we go. Makes sense, right?
  2591. 1:54:28It appears at the bottom. So, let me
  2592. 1:54:30head over into it and turn it into a
  2593. 1:54:33section. This section will have an ID of
  2594. 1:54:36experience so we can nicely scroll to it
  2595. 1:54:39from the navbar and it'll also have a
  2596. 1:54:42class name of W full so it takes the
  2597. 1:54:45full width of the screen as well as on
  2598. 1:54:47medium devices a margin top of 40 to
  2599. 1:54:50divide it from the top and typically a
  2600. 1:54:52margin top of 20. It'll have section
  2601. 1:54:56padding, so we give it some space to
  2602. 1:54:58breathe. As well as on extra-l large
  2603. 1:55:00devices, padding x will be zero because
  2604. 1:55:03we have enough space to show it all
  2605. 1:55:05around. Within this section, we'll have
  2606. 1:55:07another div that'll have a class name
  2607. 1:55:10equal to
  2608. 1:55:12w-ful age- full on medium devices
  2609. 1:55:15padding x of 20 and typically a padding
  2610. 1:55:18x of five. Now within it, we want to
  2611. 1:55:22display our header
  2612. 1:55:24title. This is the title I'm talking
  2613. 1:55:27about, my career overview, and then
  2614. 1:55:29professional experience. We'll want to
  2615. 1:55:31turn this into a new custom component as
  2616. 1:55:34we will reuse it in multiple places
  2617. 1:55:36later on. So let's do it by creating a
  2618. 1:55:40new reusable component called
  2619. 1:55:43title header.jsx.
  2620. 1:55:47run rafce and import it over within the
  2621. 1:55:50experience section title header just so
  2622. 1:55:53we can very easily and immediately see
  2623. 1:55:56it at the bottom. Then pass over a title
  2624. 1:56:01equal to
  2625. 1:56:02professional work
  2626. 1:56:04experience and also pass a sub which is
  2627. 1:56:08a subtitle that can say something like
  2628. 1:56:10my career
  2629. 1:56:13overview and maybe at the start of that
  2630. 1:56:16sub we can also display some kind of an
  2631. 1:56:18emoji maybe like a work briefcase or
  2632. 1:56:21something like that. So now we can head
  2633. 1:56:23over into that title header and we can
  2634. 1:56:26accept those props and display them on
  2635. 1:56:28the screen. So simply dstructure the
  2636. 1:56:30title and the subtitle right at the top
  2637. 1:56:34and return a div with a class name of
  2638. 1:56:37flex flex- call items-c center and a gap
  2639. 1:56:43of five. and within it render another
  2640. 1:56:46div with a class name equal to
  2641. 1:56:51hero-b within it render a p tag which
  2642. 1:56:54will render the subtitle and therefore
  2643. 1:56:57you'll be able to see this my career
  2644. 1:56:59overview if you open it up within CSS
  2645. 1:57:02you'll see that we're applying a black
  2646. 1:57:04background border some padding Y on top
  2647. 1:57:07and bottom and X for left and right we
  2648. 1:57:10are making it rounded making it fit the
  2649. 1:57:12screen making the text a bit small and
  2650. 1:57:15making it not wrap so it stays in the
  2651. 1:57:17single line. And then right below this
  2652. 1:57:20div, I'll create another div with a
  2653. 1:57:23class name of font semibold on medium
  2654. 1:57:27devices text 5 XL typically text 3 XL as
  2655. 1:57:32well as text center. And here we can
  2656. 1:57:35display that big title headline such as
  2657. 1:57:39this one professional work experience.
  2658. 1:57:41So now that we have the title for our
  2659. 1:57:44experience section, let's loop over our
  2660. 1:57:47experience cards. We can do it right
  2661. 1:57:49here below the title by creating another
  2662. 1:57:53div with a class name of margin top of
  2663. 1:57:5732 to divide it a bit from the top and a
  2664. 1:58:00class name of
  2665. 1:58:01relative. Within it, I want to create a
  2666. 1:58:04div that will act as a wrapper for our
  2667. 1:58:06experience cards. So, let's also give it
  2668. 1:58:09a class name of relative, a zindex of
  2669. 1:58:1250, and an extra-large devices space Y
  2670. 1:58:16of 32, and space Y of 10 on all other
  2671. 1:58:21devices within it. We want to map over
  2672. 1:58:23our exp cards or experience cards coming
  2673. 1:58:28from constants
  2674. 1:58:30index.js. You can see here that I
  2675. 1:58:32created three different cards. Each one
  2676. 1:58:35is an object that has a review, which is
  2677. 1:58:38just like some kind of a description of
  2678. 1:58:40what I did for that company, an image
  2679. 1:58:42path, a logo path, a title, date, and
  2680. 1:58:47responsibilities. I think this gives you
  2681. 1:58:49a pretty good idea of how you should
  2682. 1:58:51fill in this data yourself for your
  2683. 1:58:53previous positions. Once you do that,
  2684. 1:58:55you'll need to map over it. So, let's
  2685. 1:58:57say map. And for each one of these
  2686. 1:59:00cards, we want to automatically return a
  2687. 1:59:02div. Since we're mapping over it, this
  2688. 1:59:05div will have a key equal to
  2689. 1:59:08card.itle since it's unique and a class
  2690. 1:59:12name of exp card wrapper and within it
  2691. 1:59:16for now just display a card.title so we
  2692. 1:59:19can see something. There we go. I worked
  2693. 1:59:22as a React Native full stack and
  2694. 1:59:25front-end developer. And now we want to
  2695. 1:59:27make it look a bit nicer. So, let's
  2696. 1:59:29first focus on this experience card
  2697. 1:59:32right here that allows you to hover over
  2698. 1:59:35these elements and then you get this
  2699. 1:59:37nice looking border. Now, you can also
  2700. 1:59:40see that we'll reuse this same card
  2701. 1:59:43later on in customer feedback highlights
  2702. 1:59:46or testimonials from specific people.
  2703. 1:59:48So, of course, that tells you that this
  2704. 1:59:49will be a reusable
  2705. 1:59:51component. So, let's create it. I'll
  2706. 1:59:54head over to the components folder and
  2707. 1:59:57create a new card. I'll call it
  2708. 2:00:01glowcard.gsx because we have the glowing
  2709. 2:00:03border. I'll run rafce. And within the
  2710. 2:00:07experience section right here, instead
  2711. 2:00:09of rendering the title, we can render a
  2712. 2:00:12div that'll have a class name equal to
  2713. 2:00:17on extra-large devices w2 over6. So this
  2714. 2:00:21will give it a specific width on larger
  2715. 2:00:24devices. And then within it, we want to
  2716. 2:00:26render a glow card. This will be a
  2717. 2:00:29component that is not a self-closing
  2718. 2:00:31component, meaning something goes into
  2719. 2:00:34it. And what is that? Well, that can be
  2720. 2:00:37a div with a self-closing image tag
  2721. 2:00:41that'll have a source equal to
  2722. 2:00:44card.gpath with an al tag of card. And
  2723. 2:00:49then alongside passing that as the
  2724. 2:00:51children into the glow card, we can also
  2725. 2:00:53pass the card as a prop which will
  2726. 2:00:56contain all of the other card
  2727. 2:00:57properties. So let's head over into the
  2728. 2:00:59card and let's accept all of these
  2729. 2:01:01properties that we're passing into it.
  2730. 2:01:04First we get the card itself, but of
  2731. 2:01:07course we need to dstructure it. And
  2732. 2:01:10then we get the children, which refers
  2733. 2:01:12to everything that we passed over as a
  2734. 2:01:15child of that component. So here we can
  2735. 2:01:18return a div that'll have a class name
  2736. 2:01:22equal to
  2737. 2:01:24card-border
  2738. 2:01:26timeline-card rounded- excel and a
  2739. 2:01:30padding of 10. Within it, I'll display a
  2740. 2:01:33self-closing div with a class name of
  2741. 2:01:36glow. And now you can slowly start
  2742. 2:01:38seeing how this card will glow as you
  2743. 2:01:41hover over it. So let's try to look a
  2744. 2:01:44bit deeper into how this glow works.
  2745. 2:01:46Well, first of all, we remove the
  2746. 2:01:47pointer events so you don't get the
  2747. 2:01:49cursor pointer. So, it doesn't seem the
  2748. 2:01:50cards are clickable because they are
  2749. 2:01:52not. We position it absolutely over that
  2750. 2:01:56element with full width and height in
  2751. 2:01:59the middle of the screen. And then we're
  2752. 2:02:00focusing only on changing the border of
  2753. 2:02:03that card as we move to it. The code for
  2754. 2:02:05the exact border is right here. And I am
  2755. 2:02:08applying a bit of a more complex
  2756. 2:02:10gradient right here. I found this CSS
  2757. 2:02:13somewhere on the internet. It could have
  2758. 2:02:14been a really nice code pen and I saved
  2759. 2:02:17it somewhere and then I ended up using
  2760. 2:02:19it on this project. Whenever you find
  2761. 2:02:21some nice CSS, always save it. You never
  2762. 2:02:24know when it's going to come in handy.
  2763. 2:02:26So, now that we have this glow right
  2764. 2:02:28here, we actually want to display
  2765. 2:02:29something within this card. First, we
  2766. 2:02:32want to display the number of stars or
  2767. 2:02:34the rating that this person has given
  2768. 2:02:36you. So let's do that within this div
  2769. 2:02:39where I will open up a dynamic block of
  2770. 2:02:42code and say array
  2771. 2:02:45from length is
  2772. 2:02:48five and then we want to get access to
  2773. 2:02:51the index. So we're mapping over these
  2774. 2:02:53five elements. We don't care about the
  2775. 2:02:55first one. We only care about the index.
  2776. 2:02:58So you do it like this. And for each
  2777. 2:03:00one, you want to automatically return an
  2778. 2:03:02image that'll be equal to a source
  2779. 2:03:05images
  2780. 2:03:08star.png. It has to have a key of index
  2781. 2:03:11and it has to have an al tag of star and
  2782. 2:03:15a class name equal to size of five. If
  2783. 2:03:19you do this, you should be able to see
  2784. 2:03:21five stars for each one of these cards.
  2785. 2:03:24We can also style the div a bit further
  2786. 2:03:26by giving it a class name of flex items
  2787. 2:03:31center gap of one in between the stars
  2788. 2:03:34and a margin bottom of five. So now we
  2789. 2:03:38have five stars within each one of these
  2790. 2:03:40cards. We can go below this div that's
  2791. 2:03:43wrapping those stars and create another
  2792. 2:03:45div with a class name of margin bottom
  2793. 2:03:49of five. And within it, we can render a
  2794. 2:03:52piece of text that'll render the review
  2795. 2:03:55of the card. We can give it a class name
  2796. 2:03:59equal to
  2797. 2:04:00text-white 50 and
  2798. 2:04:03text-LG. So, we make it a bit larger.
  2799. 2:04:06And more importantly, right below this
  2800. 2:04:08div, we can render the children. In this
  2801. 2:04:12case, the children being the logo that
  2802. 2:04:14we're passing over right here from the
  2803. 2:04:17experience section. But now if you hover
  2804. 2:04:19over it, just one side or one border
  2805. 2:04:23actually lights up and you can see this
  2806. 2:04:26gradient that is being applied to the
  2807. 2:04:28top and bottom side of this
  2808. 2:04:30border. But now how do we actually make
  2809. 2:04:33it follow our cursor? Well, for that
  2810. 2:04:36we'll have to do some math or rather
  2811. 2:04:38we'll have to know about the position of
  2812. 2:04:40our cursor. So let me show you how to do
  2813. 2:04:43that. First, I'll create a new
  2814. 2:04:47reference to an element and I'll call it
  2815. 2:04:49card refs and I'll make it equal to use
  2816. 2:04:53ref. And at the start, it'll be equal to
  2817. 2:04:56an empty array of
  2818. 2:04:58references. Then I'll create a function
  2819. 2:05:01called handle mouse move. It'll be a
  2820. 2:05:06higher order function which means that
  2821. 2:05:08first we want to figure out over which
  2822. 2:05:10card are we moving over and then we want
  2823. 2:05:13to get access to the event of the actual
  2824. 2:05:16mouse. So that move event and then once
  2825. 2:05:19we have that we can figure out which
  2826. 2:05:21card we're on. So we'll say const card
  2827. 2:05:24is equal to card
  2828. 2:05:28refscurren and get the index of the
  2829. 2:05:30current card we're on. And if a car
  2830. 2:05:32doesn't exist, simply exit out of this
  2831. 2:05:34function. Now, of course, for this to
  2832. 2:05:36work, we have to give this div a
  2833. 2:05:40reference. And this reference will
  2834. 2:05:43actually take in the element and it'll
  2835. 2:05:46say card
  2836. 2:05:51refs.curren index is equal to the
  2837. 2:05:54element. So depending on which one we're
  2838. 2:05:56currently on, we will only move for that
  2839. 2:06:00specific card. And I'll also call the on
  2840. 2:06:02mouse move. And I'll call the handle
  2841. 2:06:06mouse move function and pass in the
  2842. 2:06:08specific index that we are moving over.
  2843. 2:06:12Make sure that we also have an index
  2844. 2:06:13here. So we have to pass an index as a
  2845. 2:06:16prop to this function. So I'll say
  2846. 2:06:19index. And from the experience section,
  2847. 2:06:22I'll also pass over the index next to
  2848. 2:06:25the card.
  2849. 2:06:27this index you have to get over right
  2850. 2:06:30here as the second parameter of the map
  2851. 2:06:33function. Great. So now that we have
  2852. 2:06:35that, as you scroll down, you'll be able
  2853. 2:06:37to see that no animation is happening.
  2854. 2:06:40We're not yet tracking anything right
  2855. 2:06:42now. We just know on which card we're
  2856. 2:06:44on. So now the magic happens. First, we
  2857. 2:06:48have to get the mouse position relative
  2858. 2:06:52to the card. So first let's get access
  2859. 2:06:54to the position of the card itself by
  2860. 2:06:56saying wctck is equal to
  2861. 2:06:59card.get
  2862. 2:07:01bounding client rect like this. Then we
  2863. 2:07:06have to get the x position of the mouse
  2864. 2:07:09by saying e dot
  2865. 2:07:12clientx minus
  2866. 2:07:14rect.ft minus rect.width divided by two.
  2867. 2:07:19I told you we'll have to do some math.
  2868. 2:07:21And we want to do the same for mouse y
  2869. 2:07:23position which is e.clienty y minus
  2870. 2:07:28wctop minus
  2871. 2:07:32rect.height over two. Not only do we
  2872. 2:07:35have to calculate the position of the
  2873. 2:07:37mouse but we also have to calculate the
  2874. 2:07:40angle. So I'll say calc the angle from
  2875. 2:07:44the center of the card. So I'll say let
  2876. 2:07:47angle is equal to math.
  2877. 2:07:51ATAN2. This 2 is a static method that
  2878. 2:07:54returns the angle in the plane in
  2879. 2:07:57radians between the positive x-axis and
  2880. 2:08:00the ray from 0 0. Yep, I told you this
  2881. 2:08:03requires some complex math, but I really
  2882. 2:08:05wanted to show you how to do this cool
  2883. 2:08:06effect on these
  2884. 2:08:08cards. So to this you pass the mouse Y
  2885. 2:08:12and then the mouse X positions. Then you
  2886. 2:08:16multiply it over with 180° divided by
  2887. 2:08:21math.pi. Then we need to adjust the
  2888. 2:08:23angle so it's in between 0 and 360. So
  2889. 2:08:27I'll say angle is equal to in
  2890. 2:08:30parenthesis angle plus 360 modulo
  2891. 2:08:34operator. So this will give us the
  2892. 2:08:36remainder of the division 360. And trust
  2893. 2:08:40me I asked Chad GPT to help a bit with
  2894. 2:08:42this. So, no worries if it's not fully
  2895. 2:08:44understandable on the first try. You can
  2896. 2:08:46even copy this block of code and ask
  2897. 2:08:48Chad GPT to explain it even further if
  2898. 2:08:51my explanations are not enough. And then
  2899. 2:08:54finally, we want to set the angle as a
  2900. 2:08:57CSS variable by saying
  2901. 2:09:00carstyle
  2902. 2:09:02setpropy d-start and set it to angle +
  2903. 2:09:0660. So this way we have nice movement
  2904. 2:09:09over our cards. And before we test it
  2905. 2:09:11out, looks like I made a mistake here. I
  2906. 2:09:13should have said if no card then return.
  2907. 2:09:16But if we do have a card, well then
  2908. 2:09:18let's do all of this math and let's make
  2909. 2:09:20it work. So now check this out. You can
  2910. 2:09:23actually hover over this and depending
  2911. 2:09:26on the angle that your mouse is pointing
  2912. 2:09:29from the center of the card, it'll
  2913. 2:09:32actually change. So if you very
  2914. 2:09:34erratically move it around the center,
  2915. 2:09:35you can see how it moves. But as you go
  2916. 2:09:38to the outside, the angle becomes
  2917. 2:09:40smoother and then you can move very
  2918. 2:09:43smoothly to different sides. Again, this
  2919. 2:09:45is a very cool effect. And do you need
  2920. 2:09:48to know this as a full stack developer?
  2921. 2:09:51Not necessarily. Nowadays, you can find
  2922. 2:09:53a lot of these cool effects online. But
  2923. 2:09:56if you want to become an amazing CSS or
  2924. 2:09:58front-end developer that delivers these
  2925. 2:10:01kinds of exceptional crazy experiences
  2926. 2:10:05and if this interests you then
  2927. 2:10:07definitely go for it. Try to understand
  2928. 2:10:09on a deeper level how this gives us the
  2929. 2:10:11angle how we're applying that angle over
  2930. 2:10:14to a CSS property and then finally how
  2931. 2:10:18once that property gets applied it
  2932. 2:10:20actually calculates the gradient that
  2933. 2:10:22we're applying to the border of this
  2934. 2:10:24card. But with that said, this is
  2935. 2:10:26looking great. And we could have totally
  2936. 2:10:29stopped here with the professional work
  2937. 2:10:31experience, but I want to dive much
  2938. 2:10:34deeper. I want to implement the full
  2939. 2:10:36history of your work, not just the
  2940. 2:10:38testimonial. So, let's do that next. But
  2941. 2:10:41first, it looks like I'm not passing the
  2942. 2:10:43proper border right here. So, it's card
  2943. 2:10:46and card border. There we go. So, now it
  2944. 2:10:50makes it look like a card. And this
  2945. 2:10:52makes the effect even better. And of
  2946. 2:10:54course, don't forget that we're only
  2947. 2:10:56halfway or maybe one/ird done with the
  2948. 2:10:59professional work experience section.
  2949. 2:11:01That's because this is only the left
  2950. 2:11:04side. On mobile, it seems like we've
  2951. 2:11:06done everything, but on desktop, well,
  2952. 2:11:09we still have to add the timeline. And
  2953. 2:11:11then to the right side, we have to add
  2954. 2:11:13the full explanation of the experience.
  2955. 2:11:16So, let's do that next. Back in the
  2956. 2:11:18experience section, right below our glow
  2957. 2:11:21card. So still within the same div right
  2958. 2:11:24below the glow card and below one more
  2959. 2:11:26div we can create another
  2960. 2:11:29div. This div will have a class name is
  2961. 2:11:33equal to on extra large devices w is 4
  2962. 2:11:37over 6. So we have essentially split the
  2963. 2:11:40screen into the first part which takes
  2964. 2:11:4326 of the screen and the second one
  2965. 2:11:45takes 46. Basically that's 1/3 and 2/3.
  2966. 2:11:49So within this div we can create another
  2967. 2:11:51div with a class name equal to flex and
  2968. 2:11:58items-start. And within it we can do
  2969. 2:12:00another div that'll have a class name
  2970. 2:12:05equal to
  2971. 2:12:07timeline-wrapper within which we can
  2972. 2:12:10render a self-closing div that'll have a
  2973. 2:12:13class name of
  2974. 2:12:16timeline. And then below that we can
  2975. 2:12:19create another div self-closing that'll
  2976. 2:12:22have a class name of gradient dash line
  2977. 2:12:27w1 and h dash full. If you save this you
  2978. 2:12:32won't see anything yet and that's
  2979. 2:12:33because we have to animate this timeline
  2980. 2:12:36with gap. So for now let's instead focus
  2981. 2:12:39on the second part of our screen which
  2982. 2:12:42are going to be the experience cards.
  2983. 2:12:44So, I'll head only one div below where
  2984. 2:12:47we have this timeline wrapper. And I'll
  2985. 2:12:50render another div that'll have a class
  2986. 2:12:54name equal to exp text flex. On
  2987. 2:13:00extra-large devices, a larger gap of 20.
  2988. 2:13:04On medium devices, a gap of 10. And
  2989. 2:13:07usually a gap of five with a position
  2990. 2:13:09relative and zindex of 20 within it. We
  2991. 2:13:13can then display another div that'll be
  2992. 2:13:15a wrapper for the image. So I'll give it
  2993. 2:13:18a class name of timeline- logo and
  2994. 2:13:21within it I'll render an image which
  2995. 2:13:24will have a source of card. Logo path
  2996. 2:13:28with an al tag of logo. So now we can
  2997. 2:13:31only see docker right here and the other
  2998. 2:13:34two are not yet there because we need to
  2999. 2:13:36fix up this gap animation of the
  3000. 2:13:38timeline. Next, we can go one div below
  3001. 2:13:41and create a div for the final card for
  3002. 2:13:44our experience section. It'll have an H1
  3003. 2:13:48right in between it. And this H1 will
  3004. 2:13:51render the
  3005. 2:13:52card.title. There we go. Full stack
  3006. 2:13:55developer. We can make it a bit larger
  3007. 2:13:57by giving it a class name of font-
  3008. 2:14:00semibold and text-3 Excel. So now if I
  3009. 2:14:04collapse this just a tiny bit, you
  3010. 2:14:06should be able to see it right here.
  3011. 2:14:08There we go. Below it, I'll display a P
  3012. 2:14:11tag that'll have a class name equal to
  3013. 2:14:16margin Y of five and text white of 50.
  3014. 2:14:20And there we can render the car date.
  3015. 2:14:24There we go. So now it says when you
  3016. 2:14:26work there, but we can also add some
  3017. 2:14:28kind of an emoji right here and maybe
  3018. 2:14:31make it a calendar so we can show the
  3019. 2:14:33date. But let's not use the current
  3020. 2:14:35date. Let's use the generic
  3021. 2:14:39calendar. There we go. Now we can do one
  3022. 2:14:42more P tag below this one that'll have a
  3023. 2:14:46class name of text dash #
  3024. 2:14:51839 CB5. So, this is a color that I like
  3025. 2:14:55for this one. And let's make it italic.
  3026. 2:14:58And it'll simply say responsibilities.
  3027. 2:15:01There we go. And now let's actually say
  3028. 2:15:04what we did there. So below the p tag
  3029. 2:15:07I'll render a ul with a class name of
  3030. 2:15:11list
  3031. 2:15:13d-disk ms of five margin top of five
  3032. 2:15:18flex flex- call a gap of five and text
  3033. 2:15:23dash white of 50. Within there we can
  3034. 2:15:27map over card
  3035. 2:15:30responsibilities.m mapap where we get
  3036. 2:15:32each individual
  3037. 2:15:34responsibility and we can now show it as
  3038. 2:15:38a list item. So this list item will have
  3039. 2:15:41a key equal to
  3040. 2:15:43responsibility a class name equal to
  3041. 2:15:47text-lg and finally it'll just render
  3042. 2:15:50the responsibility.
  3043. 2:15:53for example, developed and maintained
  3044. 2:15:55userfacing features for the hosting
  3045. 2:15:57website, collaborated with UIUX
  3046. 2:15:59designers, and optimized web
  3047. 2:16:01applications for maximum speed. Uh,
  3048. 2:16:04these are all AI generated, don't look
  3049. 2:16:07that good, but here's my quick tip for
  3050. 2:16:10how you can write these in a better way.
  3051. 2:16:12This is basically what you have
  3052. 2:16:13accomplished. First, use strong action
  3053. 2:16:16verbs. Developed, optimized,
  3054. 2:16:19implemented, right? collaborated,
  3055. 2:16:22achieved for example for specific
  3056. 2:16:24performance, right? So start with a
  3057. 2:16:26strong action verb led, work,
  3058. 2:16:28contributed, you get the idea. But then
  3059. 2:16:31bring in some tangible metrics or
  3060. 2:16:34achievable results. So instead of just
  3061. 2:16:36being generic with collaborated closely
  3062. 2:16:39or developed userfacing features, tell
  3063. 2:16:41me how much performance have you
  3064. 2:16:43improved. For example, improved app
  3065. 2:16:45performance and then try to be specific
  3066. 2:16:48even down to a percentage or a number.
  3067. 2:16:50improved front-facing app performance by
  3068. 2:16:5417%. Or maybe improve the load speeds by
  3069. 2:16:58two seconds. Whatever you do, try to be
  3070. 2:17:00specific in what you did as that
  3071. 2:17:03improves the credibility. And now that
  3072. 2:17:04we have the left side and the right
  3073. 2:17:06side, it's time to animate the timeline
  3074. 2:17:09which is going to give it that final
  3075. 2:17:11touch. So let's focus on implementing
  3076. 2:17:13that using GSA. We'll start at the top
  3077. 2:17:16of the screen. So let's start
  3078. 2:17:18implementing this gap scroll animation.
  3079. 2:17:21Uh we already have done a gap scroll
  3080. 2:17:23animation when we were building these
  3081. 2:17:26cards right here. So you know that if
  3082. 2:17:28you want to use a scroll animation, you
  3083. 2:17:30have to import a scroll trigger from
  3084. 2:17:33Gap. So right here I'll say import the
  3085. 2:17:37original GSAP library coming from GSAP.
  3086. 2:17:41I'll also want to import the use gap
  3087. 2:17:44hook coming from GSAP React. And
  3088. 2:17:47finally, we want to get access to the
  3089. 2:17:50scroll trigger, which will allow the
  3090. 2:17:52scroll animations coming from GAP scroll
  3091. 2:17:55trigger. And then you want to register
  3092. 2:17:57it by saying gap.register plugin scroll
  3093. 2:18:01trigger. There we go. And now we are
  3094. 2:18:04ready to use this use gap hook right
  3095. 2:18:07here. So let's say use
  3096. 2:18:10gap. It takes in a callback function and
  3097. 2:18:14an empty dependency array as a typical
  3098. 2:18:17React use effect. Great. So what kind of
  3099. 2:18:21animations do we want to implement here?
  3100. 2:18:23Well, first we want to reveal the left
  3101. 2:18:26side cards by sliding them in from the
  3102. 2:18:29left and fading them in. See these
  3103. 2:18:32cards? We want to slide and fade them
  3104. 2:18:35in. If I show you the finished
  3105. 2:18:37application and I reload, check this
  3106. 2:18:40out. As I scroll down, you'll notice
  3107. 2:18:43these cards slide in. There was the
  3108. 2:18:46first one, second one, and third one.
  3109. 2:18:49So, let's implement that. I'll say
  3110. 2:18:52gap. We want to animate a card. So,
  3111. 2:18:55where is this card coming from? Well,
  3112. 2:18:58even though we don't have a card on its
  3113. 2:19:00own here, we can use a gap utility.
  3114. 2:19:04Check this out. I'll say
  3115. 2:19:07gap.utils dot to array and we can take a
  3116. 2:19:11class name called a timeline card and we
  3117. 2:19:16can turn it into an array of cards and
  3118. 2:19:18then we can map over each one of these
  3119. 2:19:21cards and get access to that card
  3120. 2:19:23element.
  3121. 2:19:25Then once we actually have access to the
  3122. 2:19:28card, we can say get this card from
  3123. 2:19:32these specific animations or this
  3124. 2:19:34specific state. First, we want to move
  3125. 2:19:37the card in from the left side. So I'll
  3126. 2:19:40say
  3127. 2:19:42X% is equal to minus
  3128. 2:19:45100. The opacity will also start from
  3129. 2:19:49zero. And we want to set the origin of
  3130. 2:19:52the animation to the left side. So I'll
  3131. 2:19:55say transform origin will be left and
  3132. 2:20:00left. I also want to add a duration of 1
  3133. 2:20:04second. And ease which is a curve of how
  3134. 2:20:08we're going to animate it or the speed
  3135. 2:20:09of the animation. So I'll use the power
  3136. 2:20:12two.in out. It starts slowly and then it
  3137. 2:20:16speeds up. And finally, we want to
  3138. 2:20:19trigger the animation on scroll. So I'll
  3139. 2:20:22say scroll
  3140. 2:20:24trigger. Oh, but first this ease has to
  3141. 2:20:27be a string like
  3142. 2:20:28this. And I can trigger it on the card.
  3143. 2:20:33But when are we going to start? Well,
  3144. 2:20:35we're going to start the animation when
  3145. 2:20:38the card is 80% down the screen. So I'll
  3146. 2:20:41say start top 80%. If you now save this
  3147. 2:20:46and reload, you'll see how nicely it
  3148. 2:20:49appears once you reach the bottom part
  3149. 2:20:51of the screen. And then also another one
  3150. 2:20:54and another one. You don't want to show
  3151. 2:20:56it too soon because the person is still
  3152. 2:20:58viewing the top part. But as you're
  3153. 2:21:00scrolling down, you want to show it just
  3154. 2:21:02as it hits the screen. So you see
  3155. 2:21:04something happening. There we go. Now,
  3156. 2:21:06we want to animate the timeline. As the
  3157. 2:21:09user scrolls, we want to move from top
  3158. 2:21:11and then go down. So in this case I'll
  3159. 2:21:14use another gap
  3160. 2:21:17functionality. I'll say
  3161. 2:21:20gap.2 timeline. So that is the class
  3162. 2:21:23name we're switching. And then we want
  3163. 2:21:25to set the transform origin to bottom
  3164. 2:21:29bottom which means that it can also
  3165. 2:21:31start from the bottom. So see as I
  3166. 2:21:33scroll up it goes away. And if I scroll
  3167. 2:21:36down it appears. I'll choose an ease in
  3168. 2:21:40this case a power one and out. and add a
  3169. 2:21:43scroll
  3170. 2:21:44trigger where we want to trigger it on
  3171. 2:21:47the dot
  3172. 2:21:48timeline when the start is at top
  3173. 2:21:53center and we want to end it when we
  3174. 2:21:56reach 70% down the screen. I'm going to
  3175. 2:21:59be honest, I didn't come up with these
  3176. 2:22:01values immediately. Coming up with the
  3177. 2:22:03specific values takes time and testing.
  3178. 2:22:06though I might maybe set it to 50 and
  3179. 2:22:08play around a bit and see how it behaves
  3180. 2:22:11and then update it later on. Also, we
  3181. 2:22:14want to update the animation as the user
  3182. 2:22:16scrolls. So, I'll say on update and this
  3183. 2:22:19still goes within the scroll trigger.
  3184. 2:22:21We'll call a function that takes a look
  3185. 2:22:23at the self current animation or current
  3186. 2:22:26progress and it scales the timeline
  3187. 2:22:29height as the user scrolls. So, from one
  3188. 2:22:32to zero as the user scrolls up the
  3189. 2:22:34screen. So I'll say
  3190. 2:22:36gap.2
  3191. 2:22:38timeline and I'll set the scale y to 1
  3192. 2:22:44minus
  3193. 2:22:45self.progress. Now if you reload you
  3194. 2:22:48will start seeing the timeline as we hit
  3195. 2:22:50the center of the screen and then it'll
  3196. 2:22:53scroll down and follow us as we go.
  3197. 2:22:56Check this out. And then as you reach
  3198. 2:22:58the end, we cannot go any further right
  3199. 2:23:00now because we don't have additional
  3200. 2:23:01sections. Later on we will. But if you
  3201. 2:23:04start scrolling back up, you can see
  3202. 2:23:06that it starts disappearing. This is a
  3203. 2:23:08super cool effect, isn't it? And the
  3204. 2:23:11last part, we could also animate the
  3205. 2:23:13text on the right. We can make it look
  3206. 2:23:15something like this. Considering that
  3207. 2:23:17the text on the left scrolls from the
  3208. 2:23:19left, we don't have to move this from
  3209. 2:23:22left to right because then a lot of
  3210. 2:23:24things would be moving. We can just fade
  3211. 2:23:26this one in. So, see as we reach this
  3212. 2:23:28point, it'll slowly fade in alongside
  3213. 2:23:32this icon. One more time, check it
  3214. 2:23:35out. It faded
  3215. 2:23:38in. So, let's add that fade in
  3216. 2:23:40animation. To get started with it, I'll
  3217. 2:23:43actually copy this entire gub utils to
  3218. 2:23:47array and then I'll paste it at the
  3219. 2:23:50bottom just before the use gub hook
  3220. 2:23:53stops. In this case, we're not working
  3221. 2:23:55with the timeline card, but rather the
  3222. 2:23:58experience. So, exp text. And for each
  3223. 2:24:01piece of text, we want to run the gap
  3224. 2:24:05from we want to move it from zero
  3225. 2:24:08opacity, 0% because it's not moving.
  3226. 2:24:12Throughout the duration of 1 second,
  3227. 2:24:14we're not moving the origin, so we can
  3228. 2:24:16leave it as it is. Ease can be as it is.
  3229. 2:24:20Scroll trigger will be text and start
  3230. 2:24:22can be top 60. So, we show it a bit
  3231. 2:24:24later than we actually show the left
  3232. 2:24:28side card. Now, if I reload and I scroll
  3233. 2:24:31down, notice how it nicely appears
  3234. 2:24:34alongside this icon. Perfect. Oh, but I
  3235. 2:24:37was looking at the finished website. So,
  3236. 2:24:39let's go to the current one. Reload. And
  3237. 2:24:42same behavior. It's only that we cannot
  3238. 2:24:45see the bottom one yet because we can't
  3239. 2:24:47scroll any further yet. But as soon as
  3240. 2:24:49we add the next section on our list,
  3241. 2:24:51we'll be able to see it. And how does it
  3242. 2:24:53look like on mobile? Well, check this
  3243. 2:24:55out. We still get this timeline where it
  3244. 2:24:58nicely appears and then it starts
  3245. 2:25:00scrolling. We see the hostinger. We see
  3246. 2:25:03the testimonial. We continue scrolling
  3247. 2:25:05down and it looks wonderful here. We can
  3248. 2:25:10even see the Docker one because it has
  3249. 2:25:12more height. Perfect. Alongside the hero
  3250. 2:25:15section, I think this is the second most
  3251. 2:25:17interesting section in this portfolio
  3252. 2:25:19and the second toughest to build. So,
  3253. 2:25:22great job on coming this far into this
  3254. 2:25:24portfolio. The next section is also
  3255. 2:25:26super cool. And here we'll develop these
  3256. 2:25:29cards that have 3D tech icons that
  3257. 2:25:33allows you to move them in space and
  3258. 2:25:35time. Pretty cool, right? So, let's
  3259. 2:25:38focus on the tech stack that you know
  3260. 2:25:40and you use to build your websites with.
  3261. 2:25:42Next, I'll collapse it so we can nicely
  3262. 2:25:45get started on it. Close all of the
  3263. 2:25:47currently open files besides the app.
  3264. 2:25:49and let's focus on the tech
  3265. 2:25:53stack. Let's talk about the text stack.
  3266. 2:25:57In this case, we're talking about what
  3267. 2:25:59you can bring to the table and we'll
  3268. 2:26:01show it by using these animated 3D logos
  3269. 2:26:05for each tool that you're proficient in.
  3270. 2:26:07And of course, if we cannot find a 3D
  3271. 2:26:10icon, I'll provide you with the
  3272. 2:26:11alternative. So, you can just put an
  3273. 2:26:12image right here and it'll still look
  3274. 2:26:14great. So let's create it by creating a
  3275. 2:26:18new section within the sections folder
  3276. 2:26:21and let's call it tech
  3277. 2:26:24stack.jsx. Run rafce and import it
  3278. 2:26:28straight within the app below the
  3279. 2:26:30experience section. Perfect. That's
  3280. 2:26:33looking great. So if we head back over
  3281. 2:26:35here, we can see the text stack and we
  3282. 2:26:37can start by creating the layout. We'll
  3283. 2:26:40wrap everything with a div that has an
  3284. 2:26:42id of skills so we can easily scroll to
  3285. 2:26:45it. Then we can give it a class name of
  3286. 2:26:48flex dash center and
  3287. 2:26:52section-padding. And right within it
  3288. 2:26:54we'll render another div within which we
  3289. 2:26:57will reuse our title header component
  3290. 2:27:00for the consistency of different
  3291. 2:27:03sections. This title header requires two
  3292. 2:27:06different params or props. The first one
  3293. 2:27:09is title. So I'll say my preferred text
  3294. 2:27:13stack. And then below we'll pass a sub.
  3295. 2:27:16I'll enter some kind of an emoji right
  3296. 2:27:18here. Maybe like a deal hands emoji. And
  3297. 2:27:21say what I bring to the
  3298. 2:27:26table. Or we can say the
  3299. 2:27:29skills skills I bring to the table. And
  3300. 2:27:33there we go. We can immediately see it
  3301. 2:27:35right here. So now we can render the
  3302. 2:27:38skills grid with these 3D models. We can
  3303. 2:27:41do that right below the title header.
  3304. 2:27:44But first let's give this div a class
  3305. 2:27:46name of wful full age- full on medium
  3306. 2:27:52devices a padding x of 10 and typically
  3307. 2:27:55a padding x of five. And now right below
  3308. 2:27:58the title header, we can create another
  3309. 2:28:00div with a class name equal to tech d-
  3310. 2:28:04grid. Within this div, we'll want to
  3311. 2:28:07loop through the tech stack icons array
  3312. 2:28:09and create a component for each item. So
  3313. 2:28:12let's open up a dynamic block of code
  3314. 2:28:14and say tech stack
  3315. 2:28:16icons map where we get each individual
  3316. 2:28:20icon. And for each one, we want to
  3317. 2:28:23automatically return a div that'll have
  3318. 2:28:26a key equal to
  3319. 2:28:28icon.name. And within it, we can also
  3320. 2:28:30for now just display that icon name. So
  3321. 2:28:33if you save it, you should be able to
  3322. 2:28:35see React, Python, backend, interactive,
  3323. 2:28:38and project management. Perfect. Now to
  3324. 2:28:41make it look a bit better, we can style
  3325. 2:28:43this div, which will basically be a
  3326. 2:28:45card. So we can give it a card border.
  3327. 2:28:48We can give it a tech card class name as
  3328. 2:28:51well. Overflow of
  3329. 2:28:54hidden group. And on extra-large
  3330. 2:28:57devices, we can make it a rounded full
  3331. 2:29:00and typically rounded LG. Now, what
  3332. 2:29:04matters the most is what we show within
  3333. 2:29:06it. Instead of rendering the icon name,
  3334. 2:29:09I'll first render a self-closing div
  3335. 2:29:12that'll have a class name equal to tech
  3336. 2:29:15card animated bg. What this will do is
  3337. 2:29:20once you hover over it, it'll actually
  3338. 2:29:22animate it a bit, which is not
  3339. 2:29:23mandatory, but I think it has a cool
  3340. 2:29:25effect. After that, we can render
  3341. 2:29:27another div that'll contain the content.
  3342. 2:29:31So, let's give it a class name of tech
  3343. 2:29:33card content.
  3344. 2:29:36And within it we can create a wrapper
  3345. 2:29:38that contains the tech card experience
  3346. 2:29:42which will render the 3D model of that
  3347. 2:29:44specific tech stack icon. So let me
  3348. 2:29:47create a div with a class name equal to
  3349. 2:29:52tech
  3350. 2:29:53icon wrapper and directly within it we
  3351. 2:29:57can render a new self-closing component.
  3352. 2:29:59So let's create it under the components
  3353. 2:30:02folder. And we can create a new folder
  3354. 2:30:05and call it models as in 3D models. And
  3355. 2:30:09then within it we can create another
  3356. 2:30:11folder which we can call tech
  3357. 2:30:14logos. And within it we can create a new
  3358. 2:30:18tech
  3359. 2:30:20icon.jsx and
  3360. 2:30:23runce. Perfect. So now we have this tech
  3361. 2:30:26icon and we can import it right within
  3362. 2:30:28the text stack where we have the tech
  3363. 2:30:30icon wrapper. I'll call it tech icon
  3364. 2:30:34like this and pass in the model right
  3365. 2:30:37into it by getting it directly from the
  3366. 2:30:40icon. So, we're mapping over these text
  3367. 2:30:43stack icons. And each one of these icons
  3368. 2:30:46has a name, a path to the model that is
  3369. 2:30:49within our public models folder, a
  3370. 2:30:52scale, and a rotation in case you want
  3371. 2:30:55to change it by default. And then we're
  3372. 2:30:56passing it over here. So now, if you
  3373. 2:30:59compare it, you'll see that we have
  3374. 2:31:01different tech icons and they're not yet
  3375. 2:31:04in 3D, but they soon will be. Right
  3376. 2:31:06below this div that's a wrapper for the
  3377. 2:31:08tech icon, we can have another div
  3378. 2:31:11that'll have a class name of padding
  3379. 2:31:15x and w full. And we use these classes
  3380. 2:31:19to add horizontal padding to the text
  3381. 2:31:22and to make it take up the full width of
  3382. 2:31:24the
  3383. 2:31:25component. So within it, we can display
  3384. 2:31:28the name of that icon. So I'll say
  3385. 2:31:32icon.name. Perfect. So now we have React
  3386. 2:31:35developer right here and then we have
  3387. 2:31:37the icon which wouldn't display in the
  3388. 2:31:39middle. And let's just make sure to wrap
  3389. 2:31:41it in a P
  3390. 2:31:43tag. So if we close it like this, you
  3391. 2:31:46can see that it'll nicely get centered.
  3392. 2:31:48Now we can head over into the tech icon
  3393. 2:31:51and start implementing that rotating 3D
  3394. 2:31:54model for each one of the tech logos
  3395. 2:31:56inside of a canvas. Remember whenever
  3396. 2:31:58you have a 3D experience, you can show
  3397. 2:32:01it within a canvas. So before we get
  3398. 2:32:03started, let me explain how it works. In
  3399. 2:32:06simple terms, this is exactly what we
  3400. 2:32:09want to do. First, we want to load a GB
  3401. 2:32:12model in a gltf format. Then we want to
  3402. 2:32:16add basic lighting to it so we can see
  3403. 2:32:18it. We want to apply environment
  3404. 2:32:20reflections for realism. Wrap the model
  3405. 2:32:23in a floating animation. And then
  3406. 2:32:25optionally tweak the material like
  3407. 2:32:27setting the color on a mesh. And then we
  3408. 2:32:29want to disable zoom because there's
  3409. 2:32:31nothing to zoom into here. We just want
  3410. 2:32:33to be able to move it around. So let's
  3411. 2:32:35do just that. We'll start by loading in
  3412. 2:32:39the models right here. I'll say const
  3413. 2:32:42scene is equal to use gltf coming from
  3414. 2:32:47react 3. And I'll just get in the model
  3415. 2:32:51from the props that we're passing into
  3416. 2:32:53the function. So I'll say
  3417. 2:32:55model. Keep in mind we're passing it
  3418. 2:32:57right here. model is the icon. And then
  3419. 2:33:00I'll simply load in
  3420. 2:33:03model
  3421. 2:33:05path. Perfect. We just loaded the GB
  3422. 2:33:08model using Dre's use GLT of hook. And
  3423. 2:33:11the file path comes from the model model
  3424. 2:33:14path which is right here within our
  3425. 2:33:17constants file. So if you want to
  3426. 2:33:19compare it, check out the public folder
  3427. 2:33:22and then head over to models and then
  3428. 2:33:25you'll be able to see Python, 3JS,
  3429. 2:33:28React, Node, and Git. Perfect. Now that
  3430. 2:33:32we have loaded in the model, we want to
  3431. 2:33:34set up the canvas. So think of the
  3432. 2:33:35canvas as the starting div in the 3D
  3433. 2:33:38world. You cannot display 3GS properties
  3434. 2:33:42within a div. If you tried putting some
  3435. 2:33:44ambient lighting here like this, it
  3436. 2:33:48wouldn't work. But if you render a
  3437. 2:33:50canvas right here coming from React 3
  3438. 2:33:53fiber, then you can start using all of
  3439. 2:33:55these properties. So let's add a basic
  3440. 2:33:58fill light with an
  3441. 2:34:01intensity of about
  3442. 2:34:030.3. Once again, we can play with that
  3443. 2:34:05very soon. And right below it, I'll add
  3444. 2:34:08an
  3445. 2:34:10environment coming from React 3J with a
  3446. 2:34:14preset set to city. So there are
  3447. 2:34:17actually a couple of presets that you
  3448. 2:34:18can choose from to load your elements
  3449. 2:34:21within. This city environment will add
  3450. 2:34:23specific ambient lightning to achieve a
  3451. 2:34:26realistic scene reflection. It makes
  3452. 2:34:28metal and glossy surfaces look real with
  3453. 2:34:31minimal setup. Then we're going to wrap
  3454. 2:34:34everything in a floating animation. So
  3455. 2:34:36I'll say float coming from React
  3456. 2:34:393J and within it I'll wrap a group and
  3457. 2:34:44within that group I will render a
  3458. 2:34:47primitive specifically an object of
  3459. 2:34:51scene scene. There we go. So we can
  3460. 2:34:54already start seeing some logos right
  3461. 2:34:56here. But you can see that they're not
  3462. 2:34:57looking that good. It's bad actually. So
  3463. 2:35:01let's modify the float. I'll give it a
  3464. 2:35:04speed equal to
  3465. 2:35:065.5. And now you can see they're moving
  3466. 2:35:09around kind of. I'll also set the
  3467. 2:35:12rotation intensity to 0.5. So they're
  3468. 2:35:15moving a bit slower. And I'll also give
  3469. 2:35:17it a float intensity of around
  3470. 2:35:210.9. So now they're floating even more
  3471. 2:35:24subtly. But again, you can totally
  3472. 2:35:26change this. You can increase the
  3473. 2:35:28rotation intensity to one or maybe even
  3474. 2:35:30two if you really want them to move
  3475. 2:35:33around or keep it a bit more subtle.
  3476. 2:35:35Next, we can also play with the group by
  3477. 2:35:38giving it a scale equal to you can make
  3478. 2:35:41it two, you can make it three. Of
  3479. 2:35:43course, it's going to be too big. So, I
  3480. 2:35:46decided to provide a scale already
  3481. 2:35:49within the model. So, you can just say
  3482. 2:35:51model.scale, scale which is going to
  3483. 2:35:52scale all of these accordingly because
  3484. 2:35:54some are bigger and some are smaller by
  3485. 2:35:57default. So this is coming directly from
  3486. 2:36:00the index of the constants where you can
  3487. 2:36:03see that scale is one for react because
  3488. 2:36:05it's just the right size but for example
  3489. 2:36:08python was too large so I lowered it
  3490. 2:36:10down a bit and the node.js was too small
  3491. 2:36:13so I had to increase the scale
  3492. 2:36:15significantly. We can also do the same
  3493. 2:36:17thing with rotation. So I'll say
  3494. 2:36:19rotation is equal to model. rotation.
  3495. 2:36:23Perfect. So now they're all facing right
  3496. 2:36:26toward the user. As you can see, the
  3497. 2:36:28float component is pretty cool. It's a
  3498. 2:36:30utility from React 3J library used to
  3499. 2:36:33create a simple animation of the model
  3500. 2:36:35floating in space. And what do you say
  3501. 2:36:37that we also apply some additional light
  3502. 2:36:40to make it look a bit more intentional?
  3503. 2:36:42Let's say below the ambient light, which
  3504. 2:36:46you can see we can also play with it. If
  3505. 2:36:48I lower the intensity to zero, well, you
  3506. 2:36:51can't really see the difference. But if
  3507. 2:36:52I do it to one, they're going to shine
  3508. 2:36:54really bright, which we don't really
  3509. 2:36:56need. So, I'll keep it very
  3510. 2:36:58low. But next, we can add a
  3511. 2:37:01directional
  3512. 2:37:03light. Position
  3513. 2:37:05it around
  3514. 2:37:08555. Of course, we have to spell it
  3515. 2:37:10correctly so our app doesn't break.
  3516. 2:37:12Directional. And I'll set the
  3517. 2:37:15intensity to one. Again, makes a little
  3518. 2:37:18difference. You cannot really see it a
  3519. 2:37:20lot with the React logo, but if you pay
  3520. 2:37:22attention to the NO.js logo, you'll see
  3521. 2:37:25how the corners shine just a bit
  3522. 2:37:27brighter. And you'll see it even more
  3523. 2:37:29once we allow you to move the model
  3524. 2:37:32around or orbit it around. For that,
  3525. 2:37:36we'll use the orbit
  3526. 2:37:37controls. We have already used it
  3527. 2:37:39before. So you can just say orbit
  3528. 2:37:42controls and just pass the enable zoom
  3529. 2:37:46set to false because we don't want to
  3530. 2:37:47zoom in. We just want to be able to move
  3531. 2:37:49them around. And check this out. Just
  3532. 2:37:51like that, you can move this logo around
  3533. 2:37:54and see it in all different lights.
  3534. 2:37:57Great. Now, there's just one tweak I
  3535. 2:37:59want to do, and that is to change the
  3536. 2:38:01color of this 3JS logo. Originally, it
  3537. 2:38:04is black, but it's not really looking
  3538. 2:38:06good alongside our background. So, what
  3539. 2:38:09do you say that we tweak it just a tiny
  3540. 2:38:11bit? I'll add a use effect, which will
  3541. 2:38:14allow us to apply a specific effect as
  3542. 2:38:16our components load just initially on
  3543. 2:38:19mount. And we want to do it whenever the
  3544. 2:38:21scene changes. So, if we load the scene
  3545. 2:38:24later on, then we want to apply to a
  3546. 2:38:26specific scene. I'll then say if
  3547. 2:38:29model.name name. Oh, and don't forget to
  3548. 2:38:32import use effect coming from React.
  3549. 2:38:35That's very important. So now if model
  3550. 2:38:39name is triple equal to
  3551. 2:38:43interactive
  3552. 2:38:45developer in that case we want to set
  3553. 2:38:49the scene
  3554. 2:38:52scene.traverse. So we want to traverse a
  3555. 2:38:55child element of that scene and we want
  3556. 2:38:58to check if child is
  3557. 2:39:01mesh and if
  3558. 2:39:04childname is triple equal to
  3559. 2:39:08object_5. It took me some time to find
  3560. 2:39:10it but basically we're targeting that
  3561. 2:39:12specific model of 3JS. Then I want to
  3562. 2:39:16set the child material to be equal to
  3563. 2:39:19new three dot mesh
  3564. 2:39:24standard
  3565. 2:39:26material and I want to pass in a color
  3566. 2:39:29of white and to be able to use this
  3567. 2:39:32three make sure to import everything as
  3568. 2:39:37three from three. Perfect. So if you do
  3569. 2:39:42this, you can see that we can now change
  3570. 2:39:43the color or we can specifically target
  3571. 2:39:46the material of this specific mesh. You
  3572. 2:39:50can change it to red, but you have to
  3573. 2:39:52reload the screen. There we go. But of
  3574. 2:39:54course, we're going to go with white. It
  3575. 2:39:56looks very good on our background. There
  3576. 2:39:58we go. This is nice. Now, it's possible
  3577. 2:40:01that maybe you don't know Python and
  3578. 2:40:05you're wondering how you can replace
  3579. 2:40:06this for some other technology that you
  3580. 2:40:08want to include here. Don't worry. I'm
  3581. 2:40:10going to teach you how to do just that
  3582. 2:40:12very soon. But before that, there's just
  3583. 2:40:14one little part missing from this
  3584. 2:40:17section, and that are of course
  3585. 2:40:19animations. So, let's go back to the
  3586. 2:40:22text stack component, and let's animate
  3587. 2:40:25these icons one by one. I'll of course
  3588. 2:40:29use the use Gap hook coming from Gap
  3589. 2:40:33React. We will target it like this. And
  3590. 2:40:36within it I'll say GSAP which we have to
  3591. 2:40:39import of course. So import GAP from
  3592. 2:40:43GSAP. Oh, I think it has to be within
  3593. 2:40:46curly braces actually. So I'll say GSAP
  3594. 2:40:49from
  3595. 2:40:50two. What do I want to animate? I want
  3596. 2:40:53to animate the tech
  3597. 2:40:55card. At the start I want to move the
  3598. 2:40:58card down by 50 pixels and set the
  3599. 2:41:03opacity to zero.
  3600. 2:41:06And then at the end I wanted to move the
  3601. 2:41:10card back to the top. I want to reset
  3602. 2:41:13the opacity to one throughout the
  3603. 2:41:15duration of 1 second. Ease can be set to
  3604. 2:41:19power two.in out which we used before as
  3605. 2:41:22well. Stagger can be 0.2 which means
  3606. 2:41:26that each animation will happen 0.2
  3607. 2:41:29seconds after the other. And then we can
  3608. 2:41:31also add a scroll
  3609. 2:41:33trigger. and we want to trigger it once
  3610. 2:41:36we get to the skills section and we want
  3611. 2:41:39to start it once we reach the top center
  3612. 2:41:42part of the screen. So now if you reload
  3613. 2:41:46and start scrolling down you can see how
  3614. 2:41:48they will all load one after another.
  3615. 2:41:51But of course that looks even better on
  3616. 2:41:53desktop. So if you reload and go to the
  3617. 2:41:56bottom, check this out. 0.2 seconds for
  3618. 2:41:58each one. Let me show you one more
  3619. 2:42:01time. There we go. So they all load from
  3620. 2:42:04the bottom to the top and they also go
  3621. 2:42:06from opacity zero to opacity 1. And you
  3622. 2:42:10can see them all like
  3623. 2:42:12this. This is looking great. You can
  3624. 2:42:16also spin them around super fast. That's
  3625. 2:42:19cool. And um I guess the question is
  3626. 2:42:22what if you don't know Python? Well, I
  3627. 2:42:24prepared an additional part of this
  3628. 2:42:26section for you. So you can replace this
  3629. 2:42:28with any other static image. or no, it
  3630. 2:42:31doesn't have to be static. If you want
  3631. 2:42:33to take some time and search the web for
  3632. 2:42:36some other 3D icons of technologies that
  3633. 2:42:38you know, go ahead. Maybe go to
  3634. 2:42:40SketchFab and search for something like,
  3635. 2:42:43I don't know, Nex.js, but it might be
  3636. 2:42:46hard to find the wider version of the
  3637. 2:42:47NextG.js logo. Or maybe you want to add
  3638. 2:42:49something else. If you can find a 3D
  3639. 2:42:51model for it, just plug it in, point to
  3640. 2:42:55its path. So, head over to the constants
  3641. 2:42:58index.js JS and just add it to the
  3642. 2:43:01models, point to that 3D file, most
  3643. 2:43:03likely with a GB extension, and load it
  3644. 2:43:06in. But maybe you won't be able to find
  3645. 2:43:083D icons for all of these different
  3646. 2:43:12technologies. So, let me show you how we
  3647. 2:43:14can make it work with the regular
  3648. 2:43:16images. I'll head back to the text
  3649. 2:43:18stack, and I want to go after we finish
  3650. 2:43:20mapping over these icons, and I'll add a
  3651. 2:43:22new part. I'll once again map over the
  3652. 2:43:25text stack images this time because I
  3653. 2:43:27also prepared some images for you. I'll
  3654. 2:43:30say map. We get an individual icon. And
  3655. 2:43:34for each icon, we want to display a div.
  3656. 2:43:37This div will have a class name equal to
  3657. 2:43:41very similar to what we had above. So
  3658. 2:43:43let me actually copy it. This div right
  3659. 2:43:46here. And I will paste it here. Within
  3660. 2:43:49it, we can display a self-closing div
  3661. 2:43:52with a class name equal to tech card
  3662. 2:43:57animated bg as before. And we can open
  3663. 2:44:02up a div that'll act as the content
  3664. 2:44:05wrapper. So I'll say tech card
  3665. 2:44:09content. And then within it, a div
  3666. 2:44:12that'll have a class name of tech icon
  3667. 2:44:15wrapper. Still the same as above, but
  3668. 2:44:18the only difference is that here now we
  3669. 2:44:20can display a regular image. So source
  3670. 2:44:24of icon dot image path instead of a
  3671. 2:44:28model. And then right below it we can
  3672. 2:44:30render another div that'll have a class
  3673. 2:44:33name of padding x and wful and it'll
  3674. 2:44:38render a p tag that'll render the
  3675. 2:44:41icon.
  3676. 2:44:43So if you save this, you'll see that
  3677. 2:44:45just below we have an image variation of
  3678. 2:44:48the same thing. Now is this one better?
  3679. 2:44:51Well, of course it is. And for some you
  3680. 2:44:54can have 3D models, but for others, if
  3681. 2:44:56you cannot find the logo in the 3D
  3682. 2:44:59format, then you can use this section
  3683. 2:45:02right here because this is much easier
  3684. 2:45:04to replace. You can go to constants text
  3685. 2:45:07stack images and just download the PNG
  3686. 2:45:10image, put it in the path and then
  3687. 2:45:12change the name map over it and you're
  3688. 2:45:15good. I got you covered. I know that you
  3689. 2:45:17guys know so many technologies and I
  3690. 2:45:19wanted to make this portfolio as
  3691. 2:45:21customizable as possible for you. So,
  3692. 2:45:25for now, I'm going to use my text stack
  3693. 2:45:283D models, but you can opt for using
  3694. 2:45:31just regular images in case you want to
  3695. 2:45:33have a consistent showcase of the text
  3696. 2:45:35stack that you specifically know. And
  3697. 2:45:38that brings us to the end of the text
  3698. 2:45:40stack section, which looks great on
  3699. 2:45:42mobile as well as it does on desktop.
  3700. 2:45:45So, in the next section, let's focus on
  3701. 2:45:48the testimonial section, allowing you to
  3702. 2:45:51brag yourself a bit, not by saying good
  3703. 2:45:53things about yourself, but by showcasing
  3704. 2:45:56what other people have to say about
  3705. 2:45:59you. The testimonial section is pretty
  3706. 2:46:03simple, yet as you start hovering over
  3707. 2:46:05it, you'll see that it is pretty cool. I
  3708. 2:46:07mean, the goal of your websites is not
  3709. 2:46:10to be flashy. Things don't need to jump
  3710. 2:46:13around. They have to be minimal, but
  3711. 2:46:15still they have to showcase what you're
  3712. 2:46:16capable of, which is exactly what we do
  3713. 2:46:19with this simple testimonial section. A
  3714. 2:46:21cool thing about it is, of course, this
  3715. 2:46:24border that we have implemented. And an
  3716. 2:46:26even cooler thing is just how quickly
  3717. 2:46:29we'll be able to get it implemented now
  3718. 2:46:31that we already have this reusable card.
  3719. 2:46:34Let me show you. I'll create a new
  3720. 2:46:36section within the sections folder, and
  3721. 2:46:39I'll call it testimonials.jsx.
  3722. 2:46:42JSX. I'll run rafce and import it within
  3723. 2:46:47our
  3724. 2:46:48app.jsx right below the text stack. I'll
  3725. 2:46:50head over into it and start implementing
  3726. 2:46:53the layout. First, I'll wrap everything
  3727. 2:46:56in a semantic section tag with an ID of
  3728. 2:46:59testimonials so we can scroll to it from
  3729. 2:47:02the navbar. And I'll give it a class
  3730. 2:47:04name equal to flex- center and section
  3731. 2:47:09dash padding so it has some nice space
  3732. 2:47:11to
  3733. 2:47:12breathe. Next, I'll render a div that'll
  3734. 2:47:15wrap our title. So, let me give it a
  3735. 2:47:18class name equal to w full hful on
  3736. 2:47:22medium devices a padding x of 10 and
  3737. 2:47:25typically a padding x of 5. And within
  3738. 2:47:28it, I'll render a reusable title header
  3739. 2:47:32component with a title of what people
  3740. 2:47:37say about me. And then I'll add a
  3741. 2:47:42subtitle. Maybe we render an emoji of a
  3742. 2:47:46star. And then say
  3743. 2:47:49client feedback highlights. And if you
  3744. 2:47:53scroll down, looks good and is
  3745. 2:47:55consistent with other sections we have
  3746. 2:47:57on the screen. But now is the time that
  3747. 2:47:59we render the grid of these cards. We'll
  3748. 2:48:02use a multicolumn layout similar to this
  3749. 2:48:06masonry style card layout that you often
  3750. 2:48:09see on these websites like Pinterest
  3751. 2:48:11where you have images coming in at
  3752. 2:48:13different heights within different
  3753. 2:48:15columns. It creates this visually
  3754. 2:48:17interesting layout that says, "Hey, look
  3755. 2:48:20at how much content there is. It is
  3756. 2:48:23basically infinite because you can only
  3757. 2:48:25see half of this card here, which means
  3758. 2:48:27there's more at the top, and you can
  3759. 2:48:29only see half of this card here, which
  3760. 2:48:31means that there's more at the bottom.
  3761. 2:48:33And you want to have a similar feel with
  3762. 2:48:36your testimonial grid as well to let
  3763. 2:48:38people know that there's just so much
  3764. 2:48:40good things to say about you. So below
  3765. 2:48:42the title, I'll render a div that'll
  3766. 2:48:45have a class name equal to on large
  3767. 2:48:49devices, give me three columns, on
  3768. 2:48:52medium devices, give me two columns,
  3769. 2:48:55otherwise give me one column and give me
  3770. 2:48:59some margin top of about 16. within it
  3771. 2:49:04we can say
  3772. 2:49:05testimonials dom where we get each
  3773. 2:49:09individual
  3774. 2:49:10testimonial and for each one for now we
  3775. 2:49:13can render well let's do testimonial
  3776. 2:49:17review so I'll say P tag and render
  3777. 2:49:23testimonial
  3778. 2:49:24review of course make sure to import
  3779. 2:49:27testimonials from the constants index
  3780. 2:49:31and give each one a name a mention maybe
  3781. 2:49:34to their socials, their actual review,
  3782. 2:49:36and a path to the image. Of course,
  3783. 2:49:39you'll have to take some time to go
  3784. 2:49:40through these and actually change them
  3785. 2:49:42to the feedback that you were given.
  3786. 2:49:45Hopefully, working with you was even
  3787. 2:49:47better than working with me. So, if you
  3788. 2:49:49do that, you'll be able to see this
  3789. 2:49:51scattered feedback right here. But now,
  3790. 2:49:53we want to display it in a very nice
  3791. 2:49:56layout. And doing that will be super
  3792. 2:49:58easy. We just have to reuse the glow
  3793. 2:50:02card. Remember that card that we used
  3794. 2:50:04right before here? We'll just reuse it.
  3795. 2:50:08Let me show you
  3796. 2:50:09how. I will wrap everything in a glow
  3797. 2:50:14card. And I'll pass in some children,
  3798. 2:50:17specifically a div that has a class name
  3799. 2:50:21of flex items center and a gap of three.
  3800. 2:50:25And here I can say test for now.
  3801. 2:50:29If you do that, the website will break
  3802. 2:50:31because you also have to pass a prop to
  3803. 2:50:33the glow card of card is equal to
  3804. 2:50:37testimonial so that it knows what to
  3805. 2:50:39render. Now, if you scroll down, you'll
  3806. 2:50:41be able to see five different cards that
  3807. 2:50:45all say test at the bottom. But already
  3808. 2:50:48half of it is done. We have five stars
  3809. 2:50:50and the reviews from the testimonial are
  3810. 2:50:54getting passed over to the card. The
  3811. 2:50:56only remaining part is to show who gave
  3812. 2:50:58us that
  3813. 2:50:59feedback. So I'll create another div
  3814. 2:51:02within this inner div that'll contain an
  3815. 2:51:05image with a source of
  3816. 2:51:09testimonial img
  3817. 2:51:12path with an all tag equal to
  3818. 2:51:16testimonial name. There we go. So we can
  3819. 2:51:18see different people. Right now, we can
  3820. 2:51:21create another div right below it
  3821. 2:51:24that'll render a p tag with a class name
  3822. 2:51:28equal to
  3823. 2:51:29font-bold. And this one can say
  3824. 2:51:31testimonial name. I now see that we're
  3825. 2:51:34repeating ourselves too much.
  3826. 2:51:35Testimonial, image path, name, and so
  3827. 2:51:38on. So, let me just dstructure those
  3828. 2:51:40values. Image
  3829. 2:51:42path, name, and mentions. And now you
  3830. 2:51:47don't have to repeat yourself here. You
  3831. 2:51:49can just say image path. Here you can
  3832. 2:51:51say name. Here where you're rendering a
  3833. 2:51:53P tag, you can also render a name. But
  3834. 2:51:56now you'll have to pass all of these
  3835. 2:51:58within an object to the card because we
  3836. 2:52:00no longer have access to the original
  3837. 2:52:02testimonial. So we have to pass all of
  3838. 2:52:04them. Name, mentions, review, and image
  3839. 2:52:07path. But I think the glow card itself
  3840. 2:52:10only uses a couple of things. It uses
  3841. 2:52:12the review. Yeah, that's it. So, we just
  3842. 2:52:15need to pass over a review right here,
  3843. 2:52:18which we get over from the
  3844. 2:52:20card. If I save this and scroll to the
  3845. 2:52:24bottom, you can see that we still have
  3846. 2:52:26five stars, the review, and now we also
  3847. 2:52:29have the photo and the name of the guy.
  3848. 2:52:32But to increase the credibility even
  3849. 2:52:34further, we can render another P tag
  3850. 2:52:36here with a class name of text white 50.
  3851. 2:52:41And we can render the mention of that
  3852. 2:52:44guy or gal uh which gives us their
  3853. 2:52:47socials. So if somebody wants to verify
  3854. 2:52:49whether this guy is a real guy uh well
  3855. 2:52:52then they would have to maybe check them
  3856. 2:52:54out on Twitter or something like that.
  3857. 2:52:56Most developers nowadays have active
  3858. 2:52:58Twitter profiles. Also I noticed that
  3859. 2:53:00we're missing some space in between the
  3860. 2:53:02cards vertically on mobile devices. So
  3861. 2:53:05if you head over into the glow card, we
  3862. 2:53:08can add some additional classes to it
  3863. 2:53:10such as a margin bottom of five and also
  3864. 2:53:14break inside a void column. This is a
  3865. 2:53:18special CSS property that sets how page
  3866. 2:53:21column or region breaks should behave
  3867. 2:53:23inside a generated box. It is perfect
  3868. 2:53:26just for this use case where you have
  3869. 2:53:27different cards one on top of another.
  3870. 2:53:30So this is great. And with that, we're
  3871. 2:53:32actually done with the testimonial
  3872. 2:53:34section. So far, we have only seen it on
  3873. 2:53:37mobile, but now check it out on desktop.
  3874. 2:53:41We have this nice layout where this card
  3875. 2:53:43appears to be taking some more space
  3876. 2:53:45than this one, and so on. And the more
  3877. 2:53:48cards you add with different heights of
  3878. 2:53:50the content, the better it'll look like.
  3879. 2:53:53Remember that masonry Pinterest grid? If
  3880. 2:53:55you head over into the testimonials and
  3881. 2:53:58maybe duplicate a couple of these right
  3882. 2:54:01here and save it, you can see how it
  3883. 2:54:04adapts to the amount of content we have.
  3884. 2:54:07So, go ahead and collect those positive
  3885. 2:54:10testimonials. It's always great to have
  3886. 2:54:12some experts vouch for you. With that
  3887. 2:54:15said, we're getting very, very close to
  3888. 2:54:17the bottom of our page. Nonetheless,
  3889. 2:54:20it's going to be an amazing finishing
  3890. 2:54:22touch where we'll implement this fully
  3891. 2:54:25functional form that'll allow other
  3892. 2:54:27people to contact you and then we'll
  3893. 2:54:29implement another super cool artistic 3D
  3894. 2:54:32model that basically says, "Hey, I'm
  3895. 2:54:35going to be at my desk. Feel free to
  3896. 2:54:36send me an email." So, let's do that.
  3897. 2:54:41Next, last but not least section of the
  3898. 2:54:44portfolio and maybe the most important
  3899. 2:54:46one because without it, how would people
  3900. 2:54:48be able to reach you to hire you? So,
  3901. 2:54:51let's get started with implementing the
  3902. 2:54:53contact section. Back in our project,
  3903. 2:54:55the last section we implemented was the
  3904. 2:54:58testimonial section. So, now we have to
  3905. 2:55:00add the contact section just below. And
  3906. 2:55:03recently I saw that Jet Brains released
  3907. 2:55:05a new coding agent that can speed up our
  3908. 2:55:08workflow. So I wanted to test it a bit
  3909. 2:55:10when creating this contact section. I'll
  3910. 2:55:12press command shiftp and open up Juny.
  3911. 2:55:15That's the name of our assistant. If
  3912. 2:55:16you're using WebStorm and you want to
  3913. 2:55:18test it out with me, I'll leave the link
  3914. 2:55:20down in the description. But for now,
  3915. 2:55:21let's try to be precise. Let's say
  3916. 2:55:24create a new section called contact.
  3917. 2:55:28imported within the app and within it
  3918. 2:55:32use the class names that we typically
  3919. 2:55:37wrap all of our sections with and also
  3920. 2:55:42add a title header
  3921. 2:55:44component right within it that
  3922. 2:55:47says something about contacting us. I'll
  3923. 2:55:51turn on the brave mode and give it a go.
  3924. 2:55:54It is working right now and it actually
  3925. 2:55:56outlined a plan. Examine the current
  3926. 2:55:58structure. Look at the title header
  3927. 2:56:00component since we need to use it.
  3928. 2:56:02Create a new context file in the
  3929. 2:56:04sections directory. Implement the
  3930. 2:56:06contact component with appropriate class
  3931. 2:56:07names in the title header. And then
  3932. 2:56:09import and add the contact component to
  3933. 2:56:11app.js. So right now it's actually doing
  3934. 2:56:14all of that. It's opening all of these
  3935. 2:56:16things, editing different pages. It
  3936. 2:56:18looks like it actually created a new
  3937. 2:56:20contact section right here within
  3938. 2:56:23sections contact and it ended up using
  3939. 2:56:27the title header right here alongside
  3940. 2:56:28with some other information. Pretty
  3941. 2:56:30cool. Now it's editing the app and
  3942. 2:56:32adding the contact section right within
  3943. 2:56:34it by following the same structure we
  3944. 2:56:36used before. And it told us exactly what
  3945. 2:56:39it did right here. So we can roll back
  3946. 2:56:41or just call it a day. Now, if I save
  3947. 2:56:44this and give Juny some time to rest,
  3948. 2:56:47let's see what we have. So, it looks
  3949. 2:56:49like it did exactly what I asked it to
  3950. 2:56:51do. It created a new section right here
  3951. 2:56:54within the sections directory called
  3952. 2:56:56contact. It gave it an ID so we can
  3953. 2:56:58scroll to it alongside some default
  3954. 2:57:00class names. And it also used our title
  3955. 2:57:03header component with a title as well as
  3956. 2:57:05a sub. And it even added an emoji
  3957. 2:57:08because it noticed that that's exactly
  3958. 2:57:09what we're doing for other sections. And
  3959. 2:57:11then since I wasn't precise on what we
  3960. 2:57:13want to see below, it created a generic
  3961. 2:57:16structure where somebody can actually
  3962. 2:57:18copy this email and send it to us.
  3963. 2:57:20Pretty cool stuff that it was able to do
  3964. 2:57:22all of this. Not just generate code, but
  3965. 2:57:24also create files for us. If you didn't
  3966. 2:57:27end up using Juny just yet, that's
  3967. 2:57:29totally fine. You can pause this video
  3968. 2:57:31and copy this code right here where it
  3969. 2:57:34generated this title header. But can we
  3970. 2:57:36take it a step further? Can we maybe get
  3971. 2:57:39it to create a layout right here where
  3972. 2:57:42we're going to have a form and then also
  3973. 2:57:46it'll create a new component on the
  3974. 2:57:48right side that'll allow us to display
  3975. 2:57:50this greatl looking 3D model. Well,
  3976. 2:57:53let's give it a shot. I'll once again
  3977. 2:57:55open up Juny and say something like
  3978. 2:57:58within the contact
  3979. 2:58:00section just
  3980. 2:58:02below the title
  3981. 2:58:04header. And by the way, I haven't used
  3982. 2:58:07Juni before, so I don't know how precise
  3983. 2:58:09I have to be. So, better to be as
  3984. 2:58:11descriptive as possible, but maybe it
  3985. 2:58:13works with less info as well. I don't
  3986. 2:58:15know. So right here I'll say within the
  3987. 2:58:18context section just below the title
  3988. 2:58:20header create a layout with a grid 12
  3989. 2:58:25calls class where on the left side we'll
  3990. 2:58:29have a form that has labels and inputs
  3991. 2:58:33for for the name, the email and the
  3992. 2:58:38message and of course a submit button.
  3993. 2:58:42You can find some classes within the
  3994. 2:58:45index.css to keep it consistent. And
  3995. 2:58:48then on the
  3996. 2:58:50right side of that form create and
  3997. 2:58:53display a new
  3998. 2:58:56component called contact
  3999. 2:58:59experience within which we'll later
  4000. 2:59:04on display a
  4001. 2:59:063JS canvas by importing a computer
  4002. 2:59:12optimized.gb GB file and
  4003. 2:59:16immediately add some lights and meshes
  4004. 2:59:20so we can see it on the
  4005. 2:59:22screen. This is a super hard task and
  4006. 2:59:25I'm not expecting Juny to get it right
  4007. 2:59:27on the first time, but who knows? Let's
  4008. 2:59:30see. I love testing these AI tools as
  4009. 2:59:32they just make you that much more
  4010. 2:59:34efficient of a developer. There we go.
  4011. 2:59:37It's researching everything just to get
  4012. 2:59:39some context and then it'll try to
  4013. 2:59:41create stuff. Okay, looks like it
  4014. 2:59:43started editing the contact experience
  4015. 2:59:45part. So, if we head over to our
  4016. 2:59:47components, we can see the contact
  4017. 2:59:49experience and it is writing code right
  4018. 2:59:51now already putting in the canvas and
  4019. 2:59:54some lights and meshes and the
  4020. 2:59:55environment. Looks like it's keeping in
  4021. 2:59:57mind what we did for the hero section
  4022. 2:59:59and it's done. Juny says, "A grid layout
  4023. 3:00:02was implemented in the contact section
  4024. 3:00:04featuring a form on the left for name,
  4025. 3:00:06email, and message inputs alongside a
  4026. 3:00:09new contact experience component
  4027. 3:00:10displaying a 3D computer model using 3JS
  4028. 3:00:13on the right. The implementation is
  4029. 3:00:15error-free and all components function
  4030. 3:00:18as intended. This is super cool, but
  4031. 3:00:21let's see if that is really the case. It
  4032. 3:00:24looks like it actually generated a form
  4033. 3:00:26that is super similar to the one on the
  4034. 3:00:28final application. Just a couple of
  4035. 3:00:30tweaks and it'll be there. And it also
  4036. 3:00:33left some space right here on the right
  4037. 3:00:34to display our canvas, but we'll have to
  4038. 3:00:36make a couple of tweaks to actually get
  4039. 3:00:38it to show. So, let's explore what it
  4040. 3:00:40did within the code. If I head over to
  4041. 3:00:43the app, we still have the contact
  4042. 3:00:45section. It created some states handle
  4043. 3:00:48changes as well. Oh, so actually we can
  4044. 3:00:51already type within this form. That's
  4045. 3:00:53super cool. And we have a form where we
  4046. 3:00:55can actually submit that data. And after
  4047. 3:00:57that we have our div for the left side
  4048. 3:01:01of the form. Let's go through it
  4049. 3:01:02together. So if you haven't used Juny,
  4050. 3:01:04we can actually go ahead and write it
  4051. 3:01:06together ourselves manually. Okay, first
  4052. 3:01:08things first. Right below our title
  4053. 3:01:11header, we have a div that has a class
  4054. 3:01:14name of margin top of 16 and grid 12
  4055. 3:01:19columns.
  4056. 3:01:20Then we have a contact form left side
  4057. 3:01:23that has a div where we have on
  4058. 3:01:25extra-large devices call span of six. In
  4059. 3:01:29this case we can maybe make it five so
  4060. 3:01:31that then the right side takes a larger
  4061. 3:01:34portion of the screen. And I'll also
  4062. 3:01:35remove this part. We won't need it as
  4063. 3:01:37later on as we continue developing our
  4064. 3:01:39layout we'll make sure that it all fits
  4065. 3:01:41nicely if we're on mobile devices. So
  4066. 3:01:43within this div, I'll create another div
  4067. 3:01:46that'll have a class name equal to
  4068. 3:01:50flex-c center
  4069. 3:01:52card-border rounded- excel and a padding
  4070. 3:01:55of 10. And within it, I want to bring my
  4071. 3:01:58form. So let me copy the form and just
  4072. 3:02:01bring it a couple of steps up right here
  4073. 3:02:05within this
  4074. 3:02:06div. If you now save this, you'll notice
  4075. 3:02:09that it looks something like this. Make
  4076. 3:02:11sure to give it a class name equal to w-
  4077. 3:02:15full flex flex- call and a gap of seven.
  4078. 3:02:20And let's look into the top part. We
  4079. 3:02:22have an id of contact flex center
  4080. 3:02:25section padding. We have a div that has
  4081. 3:02:28a class name of w full h full on medium
  4082. 3:02:32devices padding x of 10 and typically
  4083. 3:02:34padding x of 5. Then just below the
  4084. 3:02:36title header, we have this div that then
  4085. 3:02:39contains the left side of the form.
  4086. 3:02:41Let's just make sure that the right side
  4087. 3:02:42is not included within that div. So for
  4088. 3:02:45the 3D experience, I'll give it an
  4089. 3:02:47extra-large call span 7 so it takes a
  4090. 3:02:50bit more space. I'll remove the call
  4091. 3:02:52span 12 and I'll set the min h to 96. So
  4092. 3:02:58now you'll see that this will take seven
  4093. 3:03:00out of 12 spaces and this form will take
  4094. 3:03:03five out of 12. But on mobile devices,
  4095. 3:03:06it'll actually take the full width of
  4096. 3:03:07the screen. I can actually explain that
  4097. 3:03:10by providing a border two and border
  4098. 3:03:13solid red. And you can see that this
  4099. 3:03:15takes this part of the screen right now.
  4100. 3:03:17But if I do the same thing for the
  4101. 3:03:20bottom one, so for the right side, it'll
  4102. 3:03:24look something like this where now it
  4103. 3:03:26takes the bottom part of the screen. But
  4104. 3:03:28as soon as you expand it, you can see
  4105. 3:03:29that this takes five out of 12 and this
  4106. 3:03:32one takes 7 out of 12. which means that
  4107. 3:03:34our layout is great. So, I'll remove
  4108. 3:03:36those borders and we can continue with
  4109. 3:03:38the rest of the code. I know that it's
  4110. 3:03:40possible that you might not have had
  4111. 3:03:42this code at the bottom. So, if this is
  4112. 3:03:44getting a bit more complicated, I'll
  4113. 3:03:46also provide a complete contact.jsx file
  4114. 3:03:49within the video kit. So, you can copy
  4115. 3:03:51it and have this form right here. But
  4116. 3:03:53with that said, don't worry because we
  4117. 3:03:55can also type it out together. Next, we
  4118. 3:03:57have the form that has a class name of W
  4119. 3:03:59full flex flex colon a gap of seven. And
  4120. 3:04:02within it, we have a div. I don't think
  4121. 3:04:05this div will need a class name for a
  4122. 3:04:07margin bottom. We can just use the gaps
  4123. 3:04:09to provide that spacing. We have a label
  4124. 3:04:12for HTML 4 for the name. We have an
  4125. 3:04:16input that has a type of text, an ID of
  4126. 3:04:19name, a placeholder of your name, and
  4127. 3:04:22some parameters that we need to manage
  4128. 3:04:24its value. After that, we have another
  4129. 3:04:27div that has a label for email and also
  4130. 3:04:30some properties for the email. And
  4131. 3:04:32finally, we have another one right at
  4132. 3:04:35the bottom that's managing the message
  4133. 3:04:37property which is actually a text area.
  4134. 3:04:40Finally, after that div containing the
  4135. 3:04:42text area, we have the button with a
  4136. 3:04:45type submit. And in this case, we can
  4137. 3:04:47style it a bit differently by rendering
  4138. 3:04:50a div right within the button that'll
  4139. 3:04:53have a class name equal to CTA button
  4140. 3:04:58group. within it, we will display a div
  4141. 3:05:02that'll have a class name of BG
  4142. 3:05:06circle. And you can notice that I'm
  4143. 3:05:08recreating the button we had at the top.
  4144. 3:05:10That's because this one will be an
  4145. 3:05:11actual submit button, whereas the
  4146. 3:05:14previous button was not really a button.
  4147. 3:05:17Technically, it was an anchor tag that
  4148. 3:05:19we used to scroll to some places. Right
  4149. 3:05:21below this div that says bg circle, I'll
  4150. 3:05:24render a p tag. That'll render a text of
  4151. 3:05:27send
  4152. 3:05:28message and it'll have a class name
  4153. 3:05:31equal to text. And below it, I'll render
  4154. 3:05:34a div with a class name equal to arrow-
  4155. 3:05:38wrapper and it'll render a self-closing
  4156. 3:05:41image with a source of forward slash
  4157. 3:05:45images/ arrow-down.
  4158. 3:05:48SVG and an all tag of arrow. If you save
  4159. 3:05:52that, we'll once again have this
  4160. 3:05:55beautiful button that allows you to send
  4161. 3:05:57the message. Then if we go a bit down
  4162. 3:06:00below the div, below the button, and
  4163. 3:06:03below the form and below two more divs,
  4164. 3:06:06we get to the second part of the screen.
  4165. 3:06:09Just a simple div with a min height
  4166. 3:06:11taking seven out of 12 spaces on extra
  4167. 3:06:14large devices. And within it, we can
  4168. 3:06:16display another div within which we'll
  4169. 3:06:18put this contact experience, which you
  4170. 3:06:21might not have yet if you haven't used
  4171. 3:06:23Juny, but don't worry because we're
  4172. 3:06:24going to create it together. This div
  4173. 3:06:26will have a class name equal to W full
  4174. 3:06:31for full width, H full for height, and a
  4175. 3:06:34background color. Let's make this a
  4176. 3:06:36bright orange color that you saw in the
  4177. 3:06:38preview of CD7 C2E.
  4178. 3:06:43If you save it, oh, it looks like we
  4179. 3:06:45already get some kind of a canvas right
  4180. 3:06:47here, or at least a background color.
  4181. 3:06:49Let's make it a hover cursor grab. So,
  4182. 3:06:53we give a user an indication that they
  4183. 3:06:55can actually move around this 3D
  4184. 3:06:57experience. Let's round up the corners a
  4185. 3:07:00bit by saying rounded 3XL. And let's the
  4186. 3:07:03overflow to hidden. If you save this,
  4187. 3:07:07this is looking great, but it looks like
  4188. 3:07:08nothing's on the screen. What if I
  4189. 3:07:10expand it? Oh, still nothing. But if I
  4190. 3:07:14drag and drop, oh, would you look at
  4191. 3:07:16that? The 3D model is actually here, but
  4192. 3:07:18we are at the bottom of the table. So,
  4193. 3:07:21it looks like Juny was actually able to
  4194. 3:07:24find a 3D model within our public folder
  4195. 3:07:27and set up 3JS, React 3 Fiber, and React
  4196. 3:07:313 Dre with Orbit controls to allow us to
  4197. 3:07:34see this model. It messed up the
  4198. 3:07:36positioning a bit, but that's totally
  4199. 3:07:38okay because we can easily fix that. So,
  4200. 3:07:40let's head over into the contact
  4201. 3:07:42experience or create it for the first
  4202. 3:07:44time if you haven't already and let's
  4203. 3:07:46see what we have over here. As before, I
  4204. 3:07:48found this model on SketchFab completely
  4205. 3:07:51for free. So, you can download it and
  4206. 3:07:53use it from here. But this time, I'll
  4207. 3:07:56actually teach you another strategy of
  4208. 3:07:58how you can autooptimize your models.
  4209. 3:08:01Check this out. If you head over to the
  4210. 3:08:04public folder and head over to models,
  4211. 3:08:06you'll see that we have a computer
  4212. 3:08:08optimized as well as the computer
  4213. 3:08:11optimized
  4214. 3:08:12transformed. If you rightclick it and
  4215. 3:08:14open it within your file system, you'll
  4216. 3:08:17notice that the computer optimized has
  4217. 3:08:20486 kilobyt whereas the computer
  4218. 3:08:23optimized transformed only has 40. That
  4219. 3:08:27is more than a 10-fold optimization.
  4220. 3:08:29Huge. So let me show you how you can
  4221. 3:08:31achieve that. As before, we'll use this
  4222. 3:08:35gltfjsx package, but this time we'll
  4223. 3:08:38also apply a t flag which transforms the
  4224. 3:08:41asset for the web. Draco prune resize.
  4225. 3:08:45It'll automatically optimize the model.
  4226. 3:08:47So within our code, just open up a
  4227. 3:08:50terminal, cd into public, and then cd
  4228. 3:08:54into models. Then run mpx gltf jsx
  4229. 3:09:00computer-
  4230. 3:09:05optimized.gb-t and press enter. You'll
  4231. 3:09:08see that this will create a new computer
  4232. 3:09:10optimized.jsx file. So move it into
  4233. 3:09:13components and then
  4234. 3:09:15models. Then head over into it to see
  4235. 3:09:18what was generated and rename it over to
  4236. 3:09:21computer.
  4237. 3:09:24You'll see that it's getting the nodes
  4238. 3:09:25and the materials by using the use gltf
  4239. 3:09:29computer optimize transformed, but make
  4240. 3:09:31sure to add forward/models right here
  4241. 3:09:34before it. So, models computer
  4242. 3:09:37optimized. And then we want to do the
  4243. 3:09:39same thing right here at the bottom
  4244. 3:09:42for/models/comput optimized. And now it
  4245. 3:09:45has two different meshes to which we can
  4246. 3:09:47also add some shadows. So, say cast
  4247. 3:09:50shadows as well as receive shadows. This
  4248. 3:09:54will have that special animated effect
  4249. 3:09:56on our model. Now, head back over to the
  4250. 3:10:00contact experience file and import that
  4251. 3:10:03model. Even though what Juny created was
  4252. 3:10:05totally working, I'll delete it just
  4253. 3:10:07because we now optimized this 3D model
  4254. 3:10:09even further. Maybe Juny would have been
  4255. 3:10:11able to do that as well if I told it to.
  4256. 3:10:14But for now, within this canvas, simply
  4257. 3:10:16create a group.
  4258. 3:10:18Give it a scale of like 0.03 because it
  4259. 3:10:22is a big model. And let's change the
  4260. 3:10:24position to something like 0 minus 1.5
  4261. 3:10:29and then minus two. I found this
  4262. 3:10:31position to work the best. And of
  4263. 3:10:33course, let's say cast shadow. Within
  4264. 3:10:36this group, you can render the computer
  4265. 3:10:39model coming from components models. So
  4266. 3:10:42in my case, if I go back to the
  4267. 3:10:43application, I can now see this
  4268. 3:10:45beautiful model right here with a lot of
  4269. 3:10:48light and not too many shadows, at least
  4270. 3:10:50not yet. But hey, it's here for you. It
  4271. 3:10:54might not be there yet as we haven't
  4272. 3:10:56added those additional things. But don't
  4273. 3:10:58worry as we're going to do that right
  4274. 3:11:00now. First, I want to look into orbit
  4275. 3:11:03controls. We will set enable zoom to
  4276. 3:11:06false and set the min polar angle to
  4277. 3:11:10math pi over 5 and max polar angle to
  4278. 3:11:14math pi / 2. This one limits how far the
  4279. 3:11:18user can rotate down and this one limits
  4280. 3:11:21how far the user can rotate up. So we
  4281. 3:11:24don't want to allow them to go too much
  4282. 3:11:25up or too much down because then the
  4283. 3:11:27scene doesn't make any sense. And now of
  4284. 3:11:29course we want to play with the lights.
  4285. 3:11:31I'll remove this environment as we don't
  4286. 3:11:33need it in this case. There we go. So
  4287. 3:11:35now this looks a bit more dull, but we
  4288. 3:11:38want to create a cozy and warm
  4289. 3:11:41environment. So let's play with the
  4290. 3:11:44ambient light with the intensity of
  4291. 3:11:470.5, a color of hash
  4292. 3:11:51FFF 4E6. That's going to give it that
  4293. 3:11:54warm tone. Let's also play with a
  4294. 3:11:56directional light of something like 553
  4295. 3:12:00with an intensity of 2.5 and a color of
  4296. 3:12:04hash
  4297. 3:12:06FFD 9 B3. Okay, we're getting there. And
  4298. 3:12:11instead of this spotlight, I'll add
  4299. 3:12:14another directional light. This time
  4300. 3:12:17with a position equal to 5
  4301. 3:12:2291. There we go. Cast shadow will be
  4302. 3:12:25turned on to true. Intensity will be
  4303. 3:12:28also set to
  4304. 3:12:292.5 and we'll add a color of hash FFD
  4305. 3:12:359B3. These lights will add that warmth.
  4306. 3:12:38And finally to actually cast shadows, we
  4307. 3:12:41cannot do it in this universe because
  4308. 3:12:43this is just a orange background, right?
  4309. 3:12:46So it's not reflecting any shadows. To
  4310. 3:12:49actually receive shadows, we need to
  4311. 3:12:52create a large flat surface under the
  4312. 3:12:55model so we have something to cast the
  4313. 3:12:57shadows on. So right below this computer
  4314. 3:13:02below the group create another group
  4315. 3:13:05with a scale property of one one and a
  4316. 3:13:11mesh that will receive
  4317. 3:13:14shadows with a position of 0 minus 1.5
  4318. 3:13:20and zero and a rotation
  4319. 3:13:24of minus math.pi pi over two and then 0
  4320. 3:13:290. I found those values to work the
  4321. 3:13:32best. And within it, we can display the
  4322. 3:13:35plane geometry. You can kind of see it
  4323. 3:13:39right here at the bottom. So, we're
  4324. 3:13:40basically adding the floor. I'll give it
  4325. 3:13:42args of 3030, which is going to make it
  4326. 3:13:46much larger, so we have more space to
  4327. 3:13:49cast shadows. And I'll also give it a
  4328. 3:13:51mesh standard
  4329. 3:13:54material with a color of hash
  4330. 3:13:58A46 B2D. Check that out. That's much
  4331. 3:14:02better. And you can kind of see it in
  4332. 3:14:03the distance right where it ends. But if
  4333. 3:14:06you're just looking at the middle of the
  4334. 3:14:07screen, you won't be able to tell the
  4335. 3:14:08difference. But we still can't see any
  4336. 3:14:10shadows, can we? Let's head over into
  4337. 3:14:13our computer model to see if maybe we
  4338. 3:14:16didn't cast the shadows somewhere. I'll
  4339. 3:14:18actually zoom it in so we can see it
  4340. 3:14:19better. Oh, I made a mistake. This was
  4341. 3:14:22supposed to be cast shadow, not shadows
  4342. 3:14:25because a mesh can only cast one shadow
  4343. 3:14:27if it's a singular object and also
  4344. 3:14:30receive shadow. So, you'll have to fix
  4345. 3:14:32this on a couple of places. So, now that
  4346. 3:14:34we know that that is good, we can also
  4347. 3:14:36take a look at our lights and everything
  4348. 3:14:37that we have within this canvas. This
  4349. 3:14:39ambient light looks good to me. Besides,
  4350. 3:14:43the light is maybe a bit too yellow. So
  4351. 3:14:45let's say 4 E6. There we go. So now we
  4352. 3:14:48have this more natural white color. And
  4353. 3:14:50you can see the shadows are being
  4354. 3:14:52applied on this computer right here,
  4355. 3:14:54rather on the table. It's just they're
  4356. 3:14:56not being applied on the floor. Looking
  4357. 3:14:58at the directional light, we have a
  4358. 3:15:00position of 553, intensity of 2.5, and
  4359. 3:15:05some nice color. This is looking good.
  4360. 3:15:07And another directional light, which is
  4361. 3:15:09also looking good to me. Finally, we
  4362. 3:15:11have a group for the computer with a
  4363. 3:15:14scale and its position and it says cast
  4364. 3:15:16shadow. Below it, we render the floor.
  4365. 3:15:20So, here I see receive shadows. No, this
  4366. 3:15:23was supposed to be receive shadow. There
  4367. 3:15:25we go. So, now this scene really pops
  4368. 3:15:28and comes to life because something in
  4369. 3:15:31our minds is telling us, hey, this
  4370. 3:15:34cannot be right. This is flying in the
  4371. 3:15:36air. We need to see some shadows because
  4372. 3:15:38the light is so bright. So there has to
  4373. 3:15:40be
  4374. 3:15:41shadow which just shows you how
  4375. 3:15:43important shadows are. But with that in
  4376. 3:15:45mind, we have now developed this get in
  4377. 3:15:47touch with me session with a form as
  4378. 3:15:50well as this nice looking 3D desk setup
  4379. 3:15:54which has this some sort of a cartoonish
  4380. 3:15:56look with nice shadows. So what do you
  4381. 3:15:58say that in the next lesson we make the
  4382. 3:16:00form functional so people looking your
  4383. 3:16:03portfolio can actually send you emails.
  4384. 3:16:08To make our form functional and to allow
  4385. 3:16:10other users to email us, we'll use
  4386. 3:16:13Email.js. It is completely free and
  4387. 3:16:15super simple to set up. So, just head
  4388. 3:16:18over to email.js. Create a free account
  4389. 3:16:21or sign in if you have an account
  4390. 3:16:23already. And once you're in, you should
  4391. 3:16:25be able to see something that looks like
  4392. 3:16:27this. Add a new service. Choose your
  4393. 3:16:30email provider. In this case, I'll go
  4394. 3:16:33with Gmail. Give it a name and copy the
  4395. 3:16:36service ID. Head back over to your app
  4396. 3:16:38and create a new env file. Within it,
  4397. 3:16:43say
  4398. 3:16:45vit_app email
  4399. 3:16:49js_ervice ID and paste the service ID
  4400. 3:16:52you just copied. Then click connect
  4401. 3:16:55account and continue. It'll ask you
  4402. 3:16:57whether you want to allow it to send
  4403. 3:16:58emails on your behalf. Select yes. It'll
  4404. 3:17:01say connected and click create service.
  4405. 3:17:04Once the service has been added, head
  4406. 3:17:06over to email templates. Create a new
  4407. 3:17:09email template. You can leave it as a
  4408. 3:17:11simple contact template. Click create.
  4409. 3:17:14And you'll have to head over to settings
  4410. 3:17:16and copy the template ID. Then within
  4411. 3:17:19your env file, create another
  4412. 3:17:22environment variable called v
  4413. 3:17:26app.js template ID and simply paste the
  4414. 3:17:29ID you just copied. Then head over to
  4415. 3:17:31account within the sidebar and copy your
  4416. 3:17:34public ID. This one you can add as vit
  4417. 3:17:38app
  4418. 3:17:39email.js public key. So you should have
  4419. 3:17:43three keys in your env. Now back within
  4420. 3:17:46our contact section. We'll have to
  4421. 3:17:49somehow get access to the values within
  4422. 3:17:51our form fields. And thankfully Juny has
  4423. 3:17:54already done that for us. We have
  4424. 3:17:56created a new use state with name,
  4425. 3:18:00email, and message fields. And we're
  4426. 3:18:02also handling the changes whenever user
  4427. 3:18:04types anything on these fields. Just to
  4428. 3:18:07make it a bit more responsive, we can
  4429. 3:18:08also add the loading state. So, const
  4430. 3:18:11loading set loading at the start set to
  4431. 3:18:15use state false. And then we can update
  4432. 3:18:18the button with the loading state. So,
  4433. 3:18:20right below where we have our button,
  4434. 3:18:22which is right here. The button can be
  4435. 3:18:25disabled if we're currently loading. So,
  4436. 3:18:27I'll say disabled if
  4437. 3:18:30loading. And we can also change the text
  4438. 3:18:33to say if we're loading, then display
  4439. 3:18:36sending dot dot dot else display send
  4440. 3:18:40message. Finally, we can head over to
  4441. 3:18:43handle submit. First, you have to
  4442. 3:18:46prevent the default behavior of the
  4443. 3:18:47browser to reload once you submit a
  4444. 3:18:49form. We don't want that within modern
  4445. 3:18:52applications. And now that we have
  4446. 3:18:53collected all of these values within
  4447. 3:18:55form data, what do you say that we go
  4448. 3:18:57ahead and hook it up to emailJS keys
  4449. 3:19:00that we just got from their dashboard?
  4450. 3:19:03To do that, you'll have to install a new
  4451. 3:19:05package by running mpmi at
  4452. 3:19:08email.js/browser
  4453. 3:19:09js/b
  4454. 3:19:12browser and then you can import it at
  4455. 3:19:14the top by saying import
  4456. 3:19:18email.js from at@
  4457. 3:19:22email.js/b browser and then within
  4458. 3:19:24handle submit we want to take the data
  4459. 3:19:26that we have but we'll make it into an
  4460. 3:19:29asynchronous function because sending an
  4461. 3:19:31email does take some time. So instead of
  4462. 3:19:34console logging, I'll just open up a try
  4463. 3:19:37and catch block. In case something goes
  4464. 3:19:40wrong, we'll get an error and I'll say
  4465. 3:19:43console log email.js error. And maybe we
  4466. 3:19:47can also display that error right here
  4467. 3:19:49next to it. And then we can also add a
  4468. 3:19:52finally block which will be executed no
  4469. 3:19:54matter whether we succeed or fail with
  4470. 3:19:56that request. And here we can just reset
  4471. 3:19:59the loading to false because we want to
  4472. 3:20:01stop it even when there's an error. And
  4473. 3:20:04then in the try we want to use email.js
  4474. 3:20:06functionality to send the form. First we
  4475. 3:20:10will trigger the loading. So at the top
  4476. 3:20:13I'll say set loading is set to true. And
  4477. 3:20:17then here we'll say await
  4478. 3:20:20email.js.end form. And you want to pass
  4479. 3:20:22all the keys that we saved. import meta.
  4480. 3:20:28env. App email.js service ID. That's the
  4481. 3:20:33first one. You want to duplicate it. The
  4482. 3:20:36second one is the template ID. So, how
  4483. 3:20:39do you want your email to look like? And
  4484. 3:20:41the third one is the public key. That's
  4485. 3:20:45everything you need to send over an
  4486. 3:20:47email. Finally, we want to reset the
  4487. 3:20:50form after submission. So, I'll bring
  4488. 3:20:53this up right here.
  4489. 3:20:55And that's more or less it. This should
  4490. 3:20:58successfully send an email. So what do
  4491. 3:21:01you say that we go ahead and test it
  4492. 3:21:03out? I've already filled in some
  4493. 3:21:05information right here. But the question
  4494. 3:21:06is where are we actually taking in this
  4495. 3:21:09form data and passing it into the send
  4496. 3:21:12form. See the way in which it works is
  4497. 3:21:14first you pass the service ID, then the
  4498. 3:21:17template ID, then the form, and finally
  4499. 3:21:20the options. So right here after your
  4500. 3:21:23second parameter, you want to pass in
  4501. 3:21:25the form data. Specifically, you want to
  4502. 3:21:28pass a reference to the form. So let's
  4503. 3:21:31create a new reference right at the top
  4504. 3:21:34by saying const form ref is equal to use
  4505. 3:21:39ref coming from react at the start set
  4506. 3:21:42to null. And we can attach this
  4507. 3:21:44reference to a form. So just scroll down
  4508. 3:21:47to where we start with our form and give
  4509. 3:21:50this form a ref equal to form ref. And
  4510. 3:21:54now right here we can pass formre
  4511. 3:21:57ref.curren which will contain all the
  4512. 3:21:59information from the form. So reload the
  4513. 3:22:03app. Head over to the contact me. Let's
  4514. 3:22:06say that your employer is contacting you
  4515. 3:22:10from
  4516. 3:22:12johndo.com and they're going to say
  4517. 3:22:14you're
  4518. 3:22:15hired. Finally, click send a message. It
  4519. 3:22:19says
  4520. 3:22:20sending. And what happened? It looks
  4521. 3:22:23like I passed the wrong template ID. So,
  4522. 3:22:25back on email.js, maybe I didn't proceed
  4523. 3:22:28with fully completing my form. I'll
  4524. 3:22:31click create. Oh yeah, I didn't save it.
  4525. 3:22:34So, I'll have to head over to settings,
  4526. 3:22:36copy the ID one more
  4527. 3:22:38time, read it to my ENVs right here, and
  4528. 3:22:44then don't forget to click save. Yep,
  4529. 3:22:47this is what matters. Now, if you reload
  4530. 3:22:50the page, scroll to the
  4531. 3:22:52contact, and try to send out the email,
  4532. 3:22:56the autofill also works. We're going to
  4533. 3:22:58say you're hired and send the message.
  4534. 3:23:02Sending. You can see that the form
  4535. 3:23:05actually get cleared, which means that
  4536. 3:23:06it successfully got sent. And if you
  4537. 3:23:09head over to your email provider, you'll
  4538. 3:23:11see a new email saying, "A message by
  4539. 3:23:13Adrian has been received. Respond at
  4540. 3:23:15your earliest convenience. You are
  4541. 3:23:18hired." And the only problem that we
  4542. 3:23:20have right now is that we don't know who
  4543. 3:23:22to respond to. What is the email that
  4544. 3:23:24they left? So if you head over to your
  4545. 3:23:26template content alongside displaying
  4546. 3:23:28the name and the message right below we
  4547. 3:23:31can also render the email. So just use a
  4548. 3:23:36double curly brace and render the email
  4549. 3:23:38variable right here. Save it. And if we
  4550. 3:23:41once again try to send out an email,
  4551. 3:23:43this time on the big
  4552. 3:23:46screen.
  4553. 3:23:48Testing. The email arrives in a second.
  4554. 3:23:51And now it also contains the email that
  4555. 3:23:54you can respond to. Perfect. We've got
  4556. 3:23:57it all covered. So to finish it off,
  4557. 3:23:59let's also add a simple footer because
  4558. 3:24:02this form and the 3D model really need
  4559. 3:24:04some breathing room at the bottom of the
  4560. 3:24:06screen.
  4561. 3:24:08We'll do something super simple that
  4562. 3:24:10looks like this where people can maybe
  4563. 3:24:11read a bit more about us. I would
  4564. 3:24:13suggest that you point this to your blog
  4565. 3:24:15and then go ahead and implement it.
  4566. 3:24:17Write about your learning journey
  4567. 3:24:19because you don't really have terms and
  4568. 3:24:21conditions. Also, put your professional
  4569. 3:24:24profiles. Okay? I wouldn't actually
  4570. 3:24:26advise going for Instagram or Facebook,
  4571. 3:24:29but X or LinkedIn or the links to your
  4572. 3:24:32blogs. Totally go for that. and then put
  4573. 3:24:34your name here. To implement this
  4574. 3:24:36footer, I'll head over into the sections
  4575. 3:24:40folder and create a new file called
  4576. 3:24:44footer.jsx, run rafce, and imported
  4577. 3:24:48right within our app by simply rendering
  4578. 3:24:50a self-closing footer component. We can
  4579. 3:24:53then head over into it and wrap it in a
  4580. 3:24:56semantic footer
  4581. 3:24:58tag that'll have a class name of footer,
  4582. 3:25:02which will automatically give it some
  4583. 3:25:03room to breathe. Within it, we can
  4584. 3:25:06render a div that'll have a class name
  4585. 3:25:09equal to
  4586. 3:25:13footer-container. And then within it,
  4587. 3:25:15another div that'll have a class name of
  4588. 3:25:19flex flex- call and justify center
  4589. 3:25:24within which we can display an anchor
  4590. 3:25:26tag with an href pointing to just
  4591. 3:25:29forward slash.
  4592. 3:25:31And this one can
  4593. 3:25:33say visit my blog. So if you really want
  4594. 3:25:37to take this portfolio further, try
  4595. 3:25:39implementing a blog section as well or
  4596. 3:25:41point to your medium blog maybe. Below
  4597. 3:25:44that div, render another div. This one
  4598. 3:25:47will have a class name of
  4599. 3:25:50socials. And here you can map over your
  4600. 3:25:53social images by saying social images
  4601. 3:25:57map where you get each individual image.
  4602. 3:26:00So I can say img and for each one render
  4603. 3:26:04an anchor
  4604. 3:26:05tag which will then render an image with
  4605. 3:26:09a source of img img path. If you do that
  4606. 3:26:15you should be able to see those four
  4607. 3:26:17icons and these social images are coming
  4608. 3:26:20from constants. So you can totally
  4609. 3:26:22change their name or even delete or add
  4610. 3:26:25some new platforms that you're using.
  4611. 3:26:28And of course, put your URL right
  4612. 3:26:31there. Let's give this anchor tag a
  4613. 3:26:33class name equal to
  4614. 3:26:37icon, a target of underscore blank, so
  4615. 3:26:41it opens up within a new tab, an href
  4616. 3:26:44pointing to the img. URL, and since
  4617. 3:26:48we're mapping over it, also a key of img
  4618. 3:26:52URL because it's unique. Finally, we can
  4619. 3:26:54head below this div of socials and add
  4620. 3:26:57another div for the right side of the
  4621. 3:26:59footer with a class name of flex flex-
  4622. 3:27:04call justify-c
  4623. 3:27:06center within which we can have a p tag
  4624. 3:27:11that'll have a class name equal to
  4625. 3:27:14text-c center and on medium devices
  4626. 3:27:18text- end and within it we can display
  4627. 3:27:21that copyright sign and Then create a
  4628. 3:27:24new date and get the current full year.
  4629. 3:27:28So that way even the next year this will
  4630. 3:27:30be updated. You can put your name right
  4631. 3:27:34here and say something like all rights
  4632. 3:27:37reserved. Well, I give you the rights
  4633. 3:27:40right here to use this as a portfolio.
  4634. 3:27:42Now we could also align this visit my
  4635. 3:27:45blog to the center. So I'll say items
  4636. 3:27:48center. There we go. So it looks good on
  4637. 3:27:52mobile devices. But if we expand it, it
  4638. 3:27:55gives just the right amount of space
  4639. 3:27:57right here for desktop devices too. But
  4640. 3:28:00here I actually want to keep it on the
  4641. 3:28:02left
  4642. 3:28:03corner. So I can say on medium devices
  4643. 3:28:06and larger items
  4644. 3:28:08start, which is going to put it here,
  4645. 3:28:11but on mobile it'll be centered. And
  4646. 3:28:14that brings us to the end of the footer
  4647. 3:28:16and almost to the end of the entire
  4648. 3:28:18build. One thing that I don't like right
  4649. 3:28:20now is that we haven't changed the
  4650. 3:28:21favicon nor the title. I want it to look
  4651. 3:28:24something a bit more like this. So to
  4652. 3:28:27change it, head over to the
  4653. 3:28:29index.html file right here and change
  4654. 3:28:33the title to something like your name
  4655. 3:28:36and m dash semicolon. Do two of these
  4656. 3:28:40and then say
  4657. 3:28:42portfolio. This will form this nice
  4658. 3:28:45Adrian longline portfolio. Also, we just
  4659. 3:28:48have to replace this favicon, which is
  4660. 3:28:50the default V1, which everybody has,
  4661. 3:28:53with the updated favicon. So, point the
  4662. 3:28:55path of the link to forward slash
  4663. 3:29:00images/fav.png. If you save it, you'll
  4664. 3:29:03see my initials right here. But, of
  4665. 3:29:05course, I don't want you to use my
  4666. 3:29:07initials. So on the right side of the
  4667. 3:29:10design, I've prepared this part right
  4668. 3:29:12here that says icons where you can
  4669. 3:29:14change some of the logos or some other
  4670. 3:29:16aspects of your portfolio. For example,
  4671. 3:29:19you can change your initials right here.
  4672. 3:29:21So to be able to change it, you'll have
  4673. 3:29:23to duplicate it. So just say duplicate
  4674. 3:29:26to your drafts and then you'll be able
  4675. 3:29:28to edit and export this icon. Once you
  4676. 3:29:32do that, you can just put it to your
  4677. 3:29:33public folder, point to it right here,
  4678. 3:29:36and then it'll update for your
  4679. 3:29:37portfolio. And with that said, we are
  4680. 3:29:40done with building our amazing
  4681. 3:29:42portfolio. So, the last step we have to
  4682. 3:29:45do is to get it deployed to the internet
  4683. 3:29:47so we can share it with
  4684. 3:29:50everyone. And we're back on Hostinger's
  4685. 3:29:53dashboard. I already got the domain name
  4686. 3:29:56with my hosting. And if you purchased
  4687. 3:29:58and selected yours, you should be able
  4688. 3:30:00to see a page that looks something like
  4689. 3:30:01this. If you didn't get the hosting or
  4690. 3:30:03the domain name yet, you can do that by
  4691. 3:30:05clicking the link down in the
  4692. 3:30:06description. Once you find your website
  4693. 3:30:08or your domain name, click file manager.
  4694. 3:30:11Then head over into your public HTML and
  4695. 3:30:14delete the default PHP because here
  4696. 3:30:17we're going to upload our website. So go
  4697. 3:30:19back to your application, stop it from
  4698. 3:30:22running and close all the terminals and
  4699. 3:30:24just run mpm run
  4700. 3:30:28build. This will build an optimized
  4701. 3:30:30production build of your application.
  4702. 3:30:32There we go. Took just 3 seconds. If you
  4703. 3:30:35check out your project, you'll notice
  4704. 3:30:36that there's a new dist as in
  4705. 3:30:38distribution folder that contains
  4706. 3:30:41everything about your website. So
  4707. 3:30:43rightclick it and open it up within your
  4708. 3:30:45finder or file explorer. Then go into
  4709. 3:30:48it, select everything within it, and
  4710. 3:30:50drag and drop it right here within your
  4711. 3:30:52H panel. There we go. Now close the H
  4712. 3:30:55panel. You'll be redirected back to your
  4713. 3:30:58domain and click on it. And that's it.
  4714. 3:31:01It loaded instantly with the proper
  4715. 3:31:03favicon title and even an HTTPS
  4716. 3:31:08certificate. Just try reloading it and
  4717. 3:31:10see how fast it is. And take a second to
  4718. 3:31:14admire everything you've built. Having
  4719. 3:31:16this custom domain name, SSL certificate
  4720. 3:31:19and fast load times definitely makes you
  4721. 3:31:22stand out in the crowd. And let's also
  4722. 3:31:24test out our email
  4723. 3:31:25functionality test from
  4724. 3:31:28production. I'll go ahead and send it.
  4725. 3:31:31And I got it in a second. Perfect. And
  4726. 3:31:34there you have it. You have successfully
  4727. 3:31:36built your own 3D personal
  4728. 3:31:40portfolio. Congratulations. Oh yeah, and
  4729. 3:31:43let's not forget that it is fully mobile
  4730. 3:31:46responsive. I mean, just look at it with
  4731. 3:31:49these nice looking timeline
  4732. 3:31:51animations. It is just amazing. Since
  4733. 3:31:55you came to the end of this one, I know
  4734. 3:31:56you love 3JS and I know you want to get
  4735. 3:31:59better at it. So, the only next step
  4736. 3:32:01that makes sense for you right now after
  4737. 3:32:03watching this video is to check out the
  4738. 3:32:06ultimate 3GS course live right now on
  4739. 3:32:09GSMy Pro. Here we'll dive super deep
  4740. 3:32:13into every single 3JS property that we
  4741. 3:32:16covered in this video and so much more
  4742. 3:32:20from different geometries, planes,
  4743. 3:32:22modifying the 3D world and lights and
  4744. 3:32:25shadows and so much more. We'll explore
  4745. 3:32:27everything that 3JS has to offer. So
  4746. 3:32:30check it out. The link is in the
  4747. 3:32:32description. With that said, thank you
  4748. 3:32:34so much for watching and I'll see you on
  4749. 3:32:36JS Mastery

About this transcript

This page contains the full transcript of Build and Deploy a Unique 3D Web Developer Portfolio with React, Three JS & GSAP by JavaScript Mastery, generated from the public captions YouTube serves with the video. The transcript has 33,454 words across 4,749 segments, with the original timestamps preserved so you can click any line to jump to that moment in the embedded player.

What you can do with it

Use the transcript to take notes, quote the speaker, build a study guide, generate a summary with ChatGPT or Claude via the YouTube Summary tool, or export it as a timed subtitle file with YouTube to SRT. You can also re-open it in the transcriber to translate the transcript into 100+ languages.

Free YouTube transcript tool

YouTube2Text is a free YouTube transcript generator — no signup, no daily limit. Paste any YouTube link and get the full transcript instantly, with timestamps, click-to-jump, translation to 100+ languages, AI prompts for ChatGPT, Claude, and Gemini, and exports to TXT, SRT, VTT, or Markdown.