Build and Deploy a Unique 3D Web Developer Portfolio with React, Three JS & GSAP — Transcript
Full transcript
- 0:00Have you ever looked at a sleek 3D
- 0:02portfolio and thought, "I wish mine
- 0:04could look that good." Well, today is
- 0:07your lucky day. Hi there, and welcome to
- 0:10a course in which you'll build a
- 0:12portfolio that could easily get featured
- 0:15on awards, combining stunning 3D
- 0:17animations with a user-friendly layout
- 0:20that'll have recruiters and clients
- 0:22saying, "Wo, this guy really knows his
- 0:26stuff." with a minimal yet effective
- 0:29navigation bar. A stunning hero section
- 0:31with a 3D room model, particles,
- 0:34animated text, and a clear call to
- 0:36action and stylish cards showcasing your
- 0:39experience, projects, and clients. After
- 0:41that, you'll display your project in a
- 0:44clean bento grid layout featuring your
- 0:46best project. Then there's a unique and
- 0:49animated timeline that visually
- 0:51showcases your career journey, sleek
- 0:54cards to highlight the technologies
- 0:56you've mastered with interactive 3D
- 0:58icons, a testimonial grid with this cool
- 1:01hover effect, and finally, a functional
- 1:04contact section featuring a form and a
- 1:07computer desk 3D model like you're
- 1:09waiting for an email and finishing it
- 1:11off with a simple footer at the end. By
- 1:14the end of this course, you'll have a
- 1:16portfolio that not only looks amazing,
- 1:19but loads smoothly, even on mobile,
- 1:21showcasing your skills and experience in
- 1:24a way that's impossible to ignore. And
- 1:26if you're worried this might be too
- 1:28advanced, well, don't worry because I'll
- 1:31walk you through everything step by step
- 1:33from integrating 3D models with 3JS and
- 1:36React 3 fiber to adding smooth
- 1:38animations with Gap and building a sleek
- 1:42modern design with Tailwind CSS. So
- 1:44whether you're a beginner looking to
- 1:46level up your portfolio or an
- 1:48experienced developer aiming for the
- 1:50next big opportunity, this video is for
- 1:52you. And alongside this video, I've also
- 1:55put together a handy cheat sheet for 3JS
- 1:58development. You can grab it for free
- 2:00within the video kit in the description
- 2:02below and use it as a quick reference.
- 2:04And if you're looking to deepen your
- 2:06understanding of 3JS and turn it into a
- 2:09career, I recently put together a course
- 2:12focused entirely on vanilla 3JS from the
- 2:15basics to advanced interactive
- 2:17experiences. You'll get handson with
- 2:20live code playgrounds built right into
- 2:23our new platform, allowing you to
- 2:24experience with lighting, animations,
- 2:27and different 3D properties in real
- 2:29time. You'll instantly see the impact of
- 2:32every change truly learning how 3JS
- 2:35works under the hood. If that sounds
- 2:37helpful, the link is in the description.
- 2:39But with that said, are you ready to
- 2:41build a portfolio that's truly next
- 2:44level?
- 2:50In this course, I won't just show you
- 2:52how to build a portfolio. I'll also
- 2:55teach you how to deploy it to the
- 2:56internet so everyone can see what you
- 2:58built. Now, a developer portfolio is not
- 3:02like any other project. It is a career
- 3:05asset designed to showcase everything
- 3:08you're capable of, making you an
- 3:10attractive candidate for any job. And
- 3:13that's why I'll teach you how to go a
- 3:16step
- 3:17further. See, hosting your portfolio on
- 3:20a personalized domain makes you more
- 3:22professional, memorable, and improves
- 3:25your credibility and search engine
- 3:27visibility. So, before we dive into
- 3:30coding, let's get that domain name. I
- 3:32highly recommend Hostinger for hosting
- 3:35your modern portfolio. And right now,
- 3:38they're offering a great deal which you
- 3:40don't want to miss. When it comes to
- 3:42hosting this modern portfolio, I
- 3:44personally go for the premium plan. And
- 3:47here's why it's perfect for this and
- 3:49many other projects. You can host 100
- 3:52websites with the same account. You get
- 3:54a free domain name, which gives you a
- 3:56professional touch. You get a free SSL
- 3:59for HTTPS security, which creates trust
- 4:02with anyone visiting your portfolio.
- 4:05This deal won't last forever. And
- 4:07because I've partnered with Hostinger,
- 4:09you get an even bigger discount. Once
- 4:11you click the link in the description,
- 4:13click claim deal and add to cart. Here,
- 4:17we'll have to choose the period of our
- 4:18hosting. With the crazy discount going
- 4:21on right now, I'll definitely choose 48
- 4:23months to save the most money and enter
- 4:27your JavaScript Mastery coupon code,
- 4:29which will give you an even bigger
- 4:30discount on your hosting package. And
- 4:33once you buy it, we are ready to set
- 4:35everything up and get started with
- 4:37building out our portfolio. Let's start
- 4:39with Hostinger's guided setup. We are
- 4:41building a new website, so we can choose
- 4:43create. And we're building a completely
- 4:45custom website, so we can choose skip at
- 4:47the bottom. Next, we need to choose our
- 4:50domain. As you can see, I use Hostinger
- 4:52for my jsmastery.pro platform. So here
- 4:56you can type out your domain name. I
- 4:58would recommend something like your
- 4:59firstname and
- 5:01lastname.com something like
- 5:05johndoey.com and if you purchase the
- 5:07hosting for more than one year you'll
- 5:09get the domain completely for free.
- 5:11Choose it and click next. The domain is
- 5:13registering. Now already we are the next
- 5:15step where you can choose where your
- 5:17audience is located at and you can see
- 5:19there's a lot of options. Click next and
- 5:22the setup will initialize. While that is
- 5:25happening, just another quick note, and
- 5:27that is that the full codebase of the
- 5:28video you're about to build is live on
- 5:31GitHub. So, you can check it out right
- 5:32here. And if you don't mind, give it a
- 5:35star. With that in mind, we are
- 5:37redirected to the dashboard of our
- 5:38hosting project. So, finally, let's code
- 5:41it out and we'll deploy it at the end of
- 5:43the course.
- 5:46To get started developing our amazing 3D
- 5:49portfolio, we'll start by spinning up
- 5:52our Vit and React project. At this
- 5:55point, I think Vit is the preferred way
- 5:57to start React applications. So, just
- 6:00head over to vit.dev, click get started,
- 6:04scroll down a bit, and you'll be able to
- 6:06see a command that we have to run. So,
- 6:08let me show you how to do that right
- 6:10within your code editor. Or maybe you're
- 6:12using an IDE like I am. Throughout this
- 6:15course, I'll be using WebStorm, a super
- 6:18powerful IDE that is, as of recently,
- 6:21completely free for non-commercial use,
- 6:24which means that you can just download
- 6:25it and start using all of its amazing
- 6:27features, you can totally proceed with
- 6:28this course with any other code editor.
- 6:30But later on, once you see something
- 6:32cool happening right here inside of my
- 6:34IDE and it's not happening for you,
- 6:36well, that's most likely WebStorm. So,
- 6:39let's open up our integrated terminal
- 6:41and run mpm
- 6:43create vit latest and then the name of
- 6:48your project. If you're not currently
- 6:49within any kind of a folder, you might
- 6:51want to type 3D portfolio right here.
- 6:54But as I'm currently within a 3D
- 6:55portfolio folder, I'll just say dot
- 6:58slash to create the project in the
- 7:00current repository. And I'll press
- 7:02enter. It'll ask me a couple of
- 7:04questions such as do I want to install
- 7:06the create V installer to which I'll say
- 7:09why sure go ahead and then it's going to
- 7:11ask me a couple of questions. The
- 7:13current directory is not empty. Well, in
- 7:16this case I'll say remove existing files
- 7:19and continue. You can choose your
- 7:22package name. I'll just say 3D
- 7:24portfolio. And then you'll need to
- 7:26choose a framework or a flavor of your
- 7:28application. You can totally proceed
- 7:30with any option from the list, but
- 7:33throughout this course, I'll go with
- 7:35React and I'll go for the JavaScript
- 7:38variant. But of course, if you want to
- 7:39do it in TypeScript, you're totally free
- 7:41to do so. That's it. Simply run mpm
- 7:44install to install all the necessary
- 7:46dependencies to run a React application.
- 7:49And once that is done, run mpm rundev to
- 7:52spin up the project on localhost 5173.
- 7:55And then just click on it to open it up
- 7:57within your browser. You should be able
- 7:59to see something that looks like this.
- 8:01But now, let's clean up our file and
- 8:03folder structure a bit so everything
- 8:05makes a bit more sense for the build of
- 8:07our 3D portfolio. I'll start by diving
- 8:10into the source folder, removing all the
- 8:12assets we have right now. Removing the
- 8:14app CSS as well as app
- 8:19JSX. We'll create all of these on our
- 8:21own. And you'll want to clean up your
- 8:23index.css as we'll update it later on.
- 8:26Now to actually show something on the
- 8:28screen, we have to create a new
- 8:31app.jsx and within it run rafce to
- 8:36quickly create a new functional
- 8:37component that'll look something like
- 8:39this. If rafce didn't work for you, that
- 8:43might mean that you don't have the
- 8:44necessary extension or a plug-in
- 8:46installed. So go to your editor
- 8:49settings, either plugins or extensions,
- 8:52and search for React snippets. In this
- 8:55case, I'm using modern React snippets,
- 8:57but any should do the trick. Once you do
- 8:59that, we can remove this import as it's
- 9:02not necessary. And we can wrap
- 9:04everything within a main tag. Within it,
- 9:06we can render a div. And within this
- 9:09div, we can render an H1. That'll say
- 9:11something like welcome to my 3D
- 9:15portfolio. Perfect. And if you go back
- 9:17to your application, you should be able
- 9:19to see something that looks like Oh. Oh,
- 9:22wait. That's too light of a screen. We
- 9:24were looking at a dark screen for such a
- 9:26long time. So, sorry for flashing you
- 9:27with this light screen. But it is what
- 9:30it is. We'll switch it over to dark mode
- 9:32very
- 9:33soon. For now, the most important part
- 9:35is that you can actually see this empty
- 9:37canvas, which throughout the period of
- 9:39this course together will turn into your
- 9:423D portfolio. But to do it, we'll need
- 9:44some packages or dependencies that will
- 9:47help us work with 3D. So open up your
- 9:50terminal one more time and actually open
- 9:54up another one. This one we'll keep
- 9:56running our application and the second
- 9:58one we'll use for installing those
- 10:00additional
- 10:01dependencies. So run mpm install. And
- 10:05now we can list out all the packages and
- 10:07dependencies we'll be using. Let's start
- 10:09with
- 10:11gap. So what is gap? Well, if you head
- 10:14over to gap.com, you'll be able to see
- 10:16that it says animate anything. A widely
- 10:19robust JavaScript animation library
- 10:22built for professionals. It allows you
- 10:24to effortlessly animate anything that JS
- 10:27can touch, delivering silky smooth
- 10:30performance and unmatched support. Okay.
- 10:33Um I think they're trying to be a bit
- 10:35flashy right here and show you
- 10:37everything that you can do with GSAP
- 10:39right here on this landing page. And I
- 10:43gotta say, it's looking great. There's a
- 10:46showcase of many different projects that
- 10:48people have built over the years. And
- 10:51who knows, maybe your portfolio ends up
- 10:54right here. So, let's install it by
- 10:56installing Gap as well as
- 11:00Gap/React. Now, after installing our
- 11:03animations library, which we'll use to
- 11:05animate specific parts of our portfolio,
- 11:08you'll also want to install 3JS, a
- 11:11library for creating WebGL powered 3D
- 11:14scenes inside of your browser. If you
- 11:17are super new to 3JS, I would recommend
- 11:20checking out our free YouTube crash
- 11:22course before proceeding with this
- 11:24course or even checking out our vanilla
- 11:273JS course on JSMy Pro. If you check out
- 11:31the terminal, you'll see we dive super
- 11:33deep into everything that 3JS offers.
- 11:36Starting from things like cameras and
- 11:38orbiting different models all the way to
- 11:41transforming and moving your objects to
- 11:44adding meshes and geometries, managing
- 11:46it all through a graphical user
- 11:48interface and of course lightning and so
- 11:51much more because we're continuously
- 11:53adding new content to this course. But
- 11:56with that said, let's add three as well
- 11:58as at
- 12:01React-3/Fiber. Fiber is a React version
- 12:04of 3JS that simplifies the use of 3JS
- 12:09library within a React
- 12:12ecosystem. And also on top of that,
- 12:14there is a package called React
- 12:183/Dre. It is a helper library that even
- 12:22further simplifies the most common 3D
- 12:24tasks like lighting, loaders, and more.
- 12:28Finally, for some scenes, we'll also
- 12:30need some visual effects like bloom and
- 12:33so on. So, I'll say add
- 12:39react-3/postprocessing. I'll explain how
- 12:41all of that works pretty soon. For now,
- 12:43let's just get them installed by
- 12:45pressing the enter key. And with that,
- 12:47our first step is done. You have
- 12:48successfully created a React application
- 12:51with an empty page that right now looks
- 12:54something like this. And you have
- 12:56installed all the necessary packages.
- 12:58There aren't many. They're going to help
- 13:00you develop your 3D portfolio. Great
- 13:04work. So, what you say that we go from
- 13:07something that looks like this to
- 13:09something that looks a bit more like
- 13:11that. Well, to achieve that, we need a
- 13:14way to apply styling to our application.
- 13:17And for styling, we'll use Tailwind CSS,
- 13:20which allows you to rapidly build modern
- 13:22websites without ever leaving your HTML.
- 13:25In other words, it's a utility first CSS
- 13:28framework packed with utility classes
- 13:30for easy but fully customizable styling.
- 13:34Something that material UI, Bootstrap,
- 13:36or other styling component libraries
- 13:38don't do very well, which is
- 13:40customization. Here you're writing
- 13:43Tailwind which is technically still just
- 13:45CSS but in a much faster way. So let's
- 13:48go ahead and set it up. Head over to
- 13:51docs installation using vit and just
- 13:54copy this first command right here and
- 13:57paste it into our second terminal mpm
- 14:00install tailwind CSS and add tailwind
- 14:04CSS/vit. After that, we'll have to go
- 14:06over to the second step, which is to
- 14:09configure the Vit plugin by adding T and
- 14:12CSS V plugin to our V configuration. So,
- 14:15just copy this entire block of code,
- 14:18navigate over to V config. By the way, I
- 14:21just pressed commandP, which allowed me
- 14:24to quickly open up the file explorer and
- 14:27navigate to this file without ever
- 14:29having to search for it right here. So
- 14:32that is command P and now you can simply
- 14:35paste what you just copied where now
- 14:38we're setting the T and CSS plugin and
- 14:40we also need to keep the previous plugin
- 14:42that we had which was react that looks
- 14:45something like this and we have to
- 14:47import this react from
- 14:53atvjs/plugin- react perfect now the
- 14:56third step is to add an import to our
- 14:59CSS file that imports tailenc CSS. So
- 15:02copy this and paste it right at the top
- 15:05of our index.css file. Add import
- 15:08tailwind CSS. And here we can also
- 15:11define the base styles like the styles
- 15:14for our HTML and body elements and give
- 15:18it a width of something like 100 VW
- 15:21which stands for viewport width. We can
- 15:24also give it a background color of black
- 15:27and we can also give it a color of
- 15:30white.
- 15:31If you save this and go back, you should
- 15:33be able to see something that looks like
- 15:35this. Welcome to my 3D portfolio now
- 15:37with a dark background and light text.
- 15:40Now, in Tailwind CSS V4 and beyond, we
- 15:44now have the native support for at theme
- 15:47directive, which allows you to define
- 15:49some global styles for fonts or colors
- 15:52right within the index. CSS without
- 15:54having to have a separate
- 15:57Tailwind.config.js file. So here you can
- 15:59do something like
- 16:01theme-ash
- 16:05color-white-50 and you can define a
- 16:08specific value like hash D9 EC FFF. This
- 16:14is a different variation of the white
- 16:16color. And instead of specifying it
- 16:18every single time D9 EC FFF, now you can
- 16:22just say color white 50. But where did
- 16:25this color come from in the first place?
- 16:27Why did I use this specific color? Well,
- 16:30alongside watching this course, I'll
- 16:32also give you access to the entire
- 16:34codebase of the course in case you want
- 16:36to just look at the code. But more
- 16:38importantly, I'll give you full and free
- 16:40access to the finalized Figma design.
- 16:44So, in case you want to check it out and
- 16:46try to attempt developing it on your own
- 16:48or you want to change some things up,
- 16:50well, you can do that by checking out
- 16:51the final Figma design. The link to the
- 16:54video kit containing everything you need
- 16:56for this video, such as the source code,
- 16:58the Figma design, and additional assets
- 17:00and snippets, is right below this
- 17:03course. So, just click on it, get to the
- 17:05video kit, and then go to the Figma
- 17:07design. From within the design, if you
- 17:09inspect specific elements of text,
- 17:11you'll see different colors such as this
- 17:13button for example that has this silver
- 17:15color or this one right here. You can
- 17:18see D9 E CFF. This is the exact color
- 17:22that I have added to my tailwind config.
- 17:24So what you can do next is go through
- 17:26the entire Figma design and manually
- 17:28apply all of these colors such as for
- 17:30example dash color-b
- 17:34blue- which is hash 2D
- 17:382D38. And then you can also add all of
- 17:40the other colors as well. Alongside the
- 17:43theming functionalities, you can also
- 17:45add the custom utility classes using
- 17:48Tailwind's layer feature. So you can say
- 17:50add layer
- 17:52utilities and then you can define for
- 17:54example a flex- center class name. Why
- 17:59flex center? Because so often we need to
- 18:02center something within a div, right? So
- 18:04instead of every time saying flex center
- 18:07center here you can just
- 18:09define that this flex center will apply
- 18:13a property of flex a justify center and
- 18:17items center. So when you want to use it
- 18:19within the code, it is super simple. You
- 18:22just say flex center. And there are many
- 18:23other ways in which we can use this new
- 18:25tailwind configuration to make your life
- 18:28much easier later on. So just so you
- 18:30don't have to go over to our design and
- 18:33poke every single element 10 times until
- 18:35you get to its color, I'll provide the
- 18:37final index.css file for you right at
- 18:40the bottom of this video kit. So head
- 18:42over there and copy the index.css file.
- 18:44Once you copy it, override everything we
- 18:47wrote here and simply paste it. You'll
- 18:50notice that here we add some additional
- 18:52elements that are going to help us style
- 18:54our application as well as some basic
- 18:57elements for defining different grids,
- 19:01responsiveness, and utility functions as
- 19:03well as most importantly theming. And
- 19:07we're adding our own font as well from
- 19:09Google Fonts. This doesn't contain any
- 19:12programming logic or 3JS code. Rather,
- 19:14it's just some utility classes that will
- 19:16make the styling of our application
- 19:18easier. Whenever there's something we're
- 19:20using within our code, I'll point your
- 19:22attention to it. For example, take a
- 19:24look at these components. This layer of
- 19:26components is used to define custom
- 19:29styles scoped to real components. This
- 19:32keeps things modular. For example, if
- 19:34you search for something like a navbar
- 19:37right here, you can see that as soon as
- 19:39we apply class name of navbar, it's
- 19:41going to apply these classes right here
- 19:43to make it look nice. And you can also
- 19:45immediately style the logo within the
- 19:47nav or even how list items within it
- 19:50look like. These styles act like
- 19:52reusable building blocks across the app.
- 19:54Think of this as the design system layer
- 19:57where you store the visual identity of
- 19:59the buttons, layouts, grids, and more.
- 20:03Don't worry, I'm going to explain how it
- 20:04all works very soon. And the only thing
- 20:06you'll need other than the styles are
- 20:09going to be the assets. Yep, these
- 20:11little icons that you can see here,
- 20:13here, here, even here. I want to make
- 20:17sure that you have everything you need
- 20:19to continue developing this application
- 20:21without going back and forth with the
- 20:22design. So, I've took the time and put
- 20:25all of these icons in a single zipped
- 20:28folder. Head over to the video kit and
- 20:30find it right here. Once you download
- 20:32it, head over to your current
- 20:33application and delete the current
- 20:35public
- 20:36folder. Unzip the folder you just
- 20:38downloaded and drag and drop this new
- 20:41public folder right within your
- 20:43application. This one will contain a lot
- 20:46of images, logos, and textures as well
- 20:49as different 3D models we'll use
- 20:51throughout our application. With that
- 20:52said, let's test whether our Tailwind
- 20:54CSS configuration works. I'll pull my
- 20:57browser to the side so we can see it
- 20:58alongside our IDE and I'll open up the
- 21:02source app.jsx and I'll apply a class
- 21:05name of
- 21:08text-3 Excel. If you do that and if your
- 21:11text gets larger or if you maybe apply
- 21:13an underline that means that Tailwind is
- 21:16properly connected to your application.
- 21:18And with that we are ready to start
- 21:20working on the hero section. So let's do
- 21:23that next.
- 21:26The portfolio hero section. The first
- 21:29thing that your users see and a major
- 21:32chance to make a first impression. Here
- 21:34we'll start strong by immediately
- 21:37blending some UI elements, scrollbased
- 21:40interactivity on the navbar and a
- 21:42realtime 3D canvas. So let me start by
- 21:45breaking it down into two major parts.
- 21:48First the JSX structure and the layout
- 21:51and then of course the GEP animations
- 21:54and interactivity. First things first,
- 21:56we have to organize our projects into
- 21:59sections and components. So let me clean
- 22:01up this app.jsx and organize our
- 22:04project. We can start by creating a
- 22:06components folder right within the
- 22:08source folder. I'll call it components
- 22:11and I'll also create another which I'll
- 22:14call sections. Next, head over into
- 22:17sections and create a new file called
- 22:21hero.jsx and run rafce and then import
- 22:25it within your app.jsx. That'll look
- 22:28something like this. We have our app. We
- 22:31can wrap it just in an empty react
- 22:33fragment for now and render a
- 22:35self-closing hero section just like
- 22:38this. But make sure to import it from
- 22:40sections hero.jsx. If you do that, you
- 22:43should be able to see just the hero
- 22:44right here on the right.
- 22:46But now we can start diving deeper into
- 22:48that hero section by creating our hero
- 22:51section layout. First we'll start with
- 22:54some semantic HTML by turning this div
- 22:58into a section. We can give the section
- 23:00an ID so we can later on scroll to it of
- 23:03hero and a class name of relative and
- 23:08overflow-hidden. Inside of it, we can
- 23:10create a div that'll have a class name
- 23:13of absolute
- 23:16top-0 left-0 and a zindex of 10. What is
- 23:21this for? Well, for a little background
- 23:24element that I want to show near the top
- 23:26left of the screen. So, let me render a
- 23:28self-closing image tag with a source of
- 23:31for/
- 23:34images/bg.png with an al tag of
- 23:37background. If you save it, you won't be
- 23:39able to see anything yet. But soon
- 23:41enough, the goal is to show these little
- 23:43dots right here floating on top left of
- 23:46the screen. So, right below this div,
- 23:48let's set up our main layout by creating
- 23:51a div with a class name equal to hero-
- 23:56layout. On the left side, we will
- 23:59display our hero content, meaning the
- 24:02text, and on the right side, we'll
- 24:04display the 3D model. So start by
- 24:07creating a semantic header
- 24:10tag with a class name of flex flex- call
- 24:16justify- center on medium devices w-
- 24:20full so it takes full screen
- 24:23wcreen and on medium devices padding x
- 24:26of 20 and padding x of five usually this
- 24:30is to make sure that it looks good on
- 24:32mobile devices as well. And now you can
- 24:34start seeing that background appear
- 24:36right here. Within it, we can display a
- 24:38div that'll have a class name equal to
- 24:42flex, flex- call, and a gap of seven.
- 24:46And now we can focus on the main
- 24:48headline. Yep, this is the one that I'm
- 24:50talking about. It'll actually be
- 24:52comprised of three different H1
- 24:54elements. So let's first start by
- 24:57creating a new div for it. It'll have a
- 24:59class name of
- 25:02hero-ext. And then here we can create
- 25:04our first H1 of shaping. Below it we can
- 25:08say into real projects. And below it we
- 25:11can say that deliver results. But we're
- 25:16shaping what? But what are we shaping?
- 25:20Well, if you take a look at the final
- 25:21application, you'll see that here we'll
- 25:24have animated text that says ideas,
- 25:27code, designs, concepts, and more. So,
- 25:31for now, let's just use one of these.
- 25:32For example, designs. And later on, I'll
- 25:36show you how we can make it animated.
- 25:38First, we need to define some kind of an
- 25:40array that we want to loop over. We
- 25:42might as well define it here by saying
- 25:44const words is equal to an array of
- 25:48different objects. And each word has to
- 25:51have a text, for example, ideas. And
- 25:54then we also have to give it an image
- 25:56path because each word will also have a
- 26:00little icon that you can see alongside
- 26:02that word. So let's give it an image
- 26:04path of forward slash
- 26:10images/id.svg. And you can do the same
- 26:12thing with the second one which is going
- 26:14to be
- 26:15concepts. And here you can see
- 26:17concepts.svg that SVG. Perfect. So now
- 26:21I'm going to zoom this out so it seems
- 26:23more like a real application. And I want
- 26:26to figure out how we can animate it.
- 26:28Well, first of all, let's map over those
- 26:30words right here. So after the word
- 26:33shaping, instead of designs, I'll open
- 26:36up a new span
- 26:38element. It'll look like this. A span
- 26:42with a class name equal to slide.
- 26:46within it another span with a class name
- 26:50equal to
- 26:51wrapper and within it we want to map
- 26:54over our words by saying words.m map
- 26:58where we get each individual word and
- 27:00for each word we want to automatically
- 27:03return a span. Now what do I mean when I
- 27:06say automatically return? I mean not
- 27:09open up a curly brace here, rather just
- 27:12open up a parenthesy because that means
- 27:14an immediate return.
- 27:17This span since we're mapping over it
- 27:19has to have a key of something like
- 27:24word.ext because each text is unique and
- 27:27a class name of flex items center on
- 27:31medium devices a gap of three typically
- 27:34a gap of one and a padding bottom of two
- 27:38just to create some spacing. Finally
- 27:40within that span we can render an image
- 27:42for the icon. It'll have a source of
- 27:44word img path. It'll have an al tag of
- 27:50word.ext and it'll have a class name of
- 27:55on extra-large devices size of 12. On
- 27:58medium devices, size of 10 and typically
- 28:02a size of seven. So it looks good on all
- 28:05different screen sizes. On medium
- 28:07devices, we can also give it some
- 28:08padding of two. typically a padding of
- 28:12one rounded dash full so it sits within
- 28:15a circle with a bg of 50. So if I save
- 28:20this you can now see two different icons
- 28:23that are animating up and down. And of
- 28:27course alongside the icon right below it
- 28:30I want to display another span element
- 28:32that'll simply render the
- 28:35word.ext. So now it says ideas and it
- 28:38animates over to concepts. But keeping
- 28:41this list of different words right here
- 28:44on top is not that ideal because what if
- 28:47we want to have not just two different
- 28:49words but rather five, six or 10. It
- 28:53doesn't make sense to clutter the view
- 28:55of the hero with some data points. And
- 28:57as a matter of fact, if you wanted
- 28:59somebody to very easily be able to
- 29:01update this, doesn't make sense to keep
- 29:03it here. It's better to extract all
- 29:05static data into a standalone file
- 29:08typically called constants which
- 29:10contains nothing more than just some
- 29:12data that we want to use across our
- 29:14application. So let's copy these words
- 29:18and instead of keeping them here let's
- 29:20create a new file within the source
- 29:23folder or rather a new folder called
- 29:26constants and within constants create a
- 29:29new file called index.js
- 29:32JS within it. Paste those words and make
- 29:35sure to export them by saying export
- 29:38const words words. And now when using
- 29:41those words, you can import them over
- 29:44from the constants. Everything still
- 29:46works as it did, but now we have just a
- 29:48single import at the top. And now if you
- 29:51wanted to provide more words right here,
- 29:53you can do that easily. As a matter of
- 29:55fact, we can add or shape concepts,
- 29:59designs, code, ideas, and I went ahead
- 30:02and duplicated it right here just so we
- 30:04have more things to scroll over in a
- 30:06more dynamic way. And then when it
- 30:09reaches the end, it'll very nicely
- 30:11scroll all the way to the first one.
- 30:14There we go. Super nice, right? So
- 30:17throughout this application, there's
- 30:18going to be more cases where we want to
- 30:20have some static code like arrays of
- 30:22different data that we want to use. And
- 30:24you can find those at the bottom of the
- 30:26video kit as well. Just copy the
- 30:28constants index.js file and just paste
- 30:31it here. You'll notice that we have
- 30:34different words, but then also you can
- 30:36change your abilities. For example, what
- 30:38are your strengths and descriptions of
- 30:40those strengths? Which different images
- 30:43do we want to import? Maybe you can also
- 30:45change different navigation links. It is
- 30:47all right here. So now that we have
- 30:49that, I want to take a second to explain
- 30:52how this animation works. See this span
- 30:55has a class name of slide and then a
- 30:58span inside of it has a class name of
- 31:00wrapper. These classes are defined
- 31:03within the index.css file. So if you
- 31:06commandclick them, I think this should
- 31:08lead you directly to where they're
- 31:10mentioned or you can just find them
- 31:12within the index.css. CSS and here you
- 31:14can see that we're defining the display
- 31:17of the entire block as inline block. We
- 31:20set the direction of column and then we
- 31:22start a transition. The key part right
- 31:25here is this key frames word slider
- 31:29where we're translating the Y position
- 31:31of the different words right here such
- 31:33as designs, code and so on. First by a
- 31:37little then more and then more and then
- 31:40we finally fully transform it. and then
- 31:42we animated over a specific number of
- 31:44seconds infinitely. This subtle yet
- 31:47powerful micro interaction brings life
- 31:50to your main headline and immediately
- 31:53communicates what you do both visually
- 31:56and interactively. And then below this
- 31:58slide with the wrapper H1, we can also
- 32:02add a P tag that can say something like,
- 32:06"Hi, I'm Adrian, a developer based in
- 32:10Croatia with a passion for code." Of
- 32:14course, you can type something
- 32:15different. Make sure to give this P tag
- 32:18a class name of text-w50.
- 32:22on medium devices text-
- 32:25XL relative a Z of 10 and pointer events
- 32:30none to make it non-clickable. And of
- 32:33course, this was supposed to be outside
- 32:35of the H1, not just the first one, but
- 32:38all of them. So, I'll post it right
- 32:40below this div that says hero text.
- 32:44There we go. Much better. Finally, below
- 32:46this, we want to create some kind of a
- 32:48call to action where people can click on
- 32:51a button and then that button leads us
- 32:54to our work. This button will be reused
- 32:57many times throughout our application.
- 32:59So, it makes most sense to create a
- 33:02reusable button component. So, back in
- 33:04our application, let's head over into
- 33:06the components folder and create our
- 33:09first component called button.jsx.
- 33:13This button will be used across multiple
- 33:15sections both in the hero and contact
- 33:18section. So let's make it fully reusable
- 33:20and dynamic. I'll run rafce to quickly
- 33:24spin up our functional component. And
- 33:26then within it we can make this button
- 33:29an anchor tag. This anchor tag will have
- 33:31a class name which will be equal to
- 33:35CTA-wrapper. CTA stands for call to
- 33:38action which means an action that you
- 33:39want your user to take. Before I
- 33:41continue developing this button, let's
- 33:43actually use it within our hero section.
- 33:45So, right below this B tag, I will
- 33:48render our button. Make sure to import
- 33:50it at the top. And then within this
- 33:52anchor tag, I can create a div with a
- 33:55class name equal to CTA group and group.
- 34:00And within it, we can render another
- 34:02div. This time, a self-closing one with
- 34:05a class name of
- 34:07BG- circle. Next, below it, we can
- 34:10render a P tag with a class name of
- 34:14text. And we can simply render something
- 34:17that should say like see my work. And
- 34:21there we go. See my work is right here.
- 34:23Below it, let's also render a div with a
- 34:26class
- 34:27name equal to arrow-wrapper.
- 34:32And let's render an image with a source
- 34:34of forward slash images slash
- 34:38arrow-down. SVG with an al tag of arrow.
- 34:42We cannot see it yet and that's because
- 34:44we have to style it a bit better. So
- 34:46from the hero section, let's pass some
- 34:48props that allow us to style it. I'll
- 34:51pass over a class name equal to on
- 34:54medium devices W of 80. on median
- 34:58devices H of 16 typically W60 and H of
- 35:0312. And why do we have to provide the
- 35:05class name as a prop and not immediately
- 35:07right here? Well, that's because this
- 35:09button is going to be used in different
- 35:11scenarios. And depending on where it's
- 35:13being used, we want to provide some
- 35:15different class names. We can also give
- 35:16it an ID of a button. And we can provide
- 35:19a text such as see my work because in
- 35:23the contact section, it's going to say
- 35:25something else. So now we can head over
- 35:27into that button and dstructure those
- 35:30props that we're passing into it such as
- 35:33the text, the class name, and the
- 35:37ID. Next, we can make this first class
- 35:40name dynamic. I'll delete it and make it
- 35:43dynamic by opening up a new dynamic
- 35:45block of code and a template string.
- 35:48I'll say if a class name exists. So
- 35:51class name and then question mark
- 35:53question mark else render an empty
- 35:55string. What does this mean? If a class
- 35:58name exists render it. If it doesn't
- 36:00render an empty string and then always
- 36:03render a CTA wrapper. Finally here
- 36:07instead of see my work we can render the
- 36:09text. And in this case, the class name
- 36:12here should have been CTA button instead
- 36:15of a CTA group, which will activate
- 36:17those classes. If you head over to that,
- 36:20you'll see that it'll apply some
- 36:21padding, both horizontal and vertical.
- 36:24It'll round the corners a bit, apply a
- 36:27dark background. But hey, if it's dark,
- 36:29why is it white right now? Well, that's
- 36:31because we also have a transition. So as
- 36:34you hover over it, you can see that the
- 36:36background turns dark and only the arrow
- 36:38jumps up and down. That happens because
- 36:41of these inner classes such as BG circle
- 36:43and the arrow wrapper. So with that in
- 36:45mind, our button is fully styled and
- 36:48later on once we implement a couple more
- 36:50sections, we'll actually make it scroll
- 36:52to the sections just below. Now we can
- 36:54get back to the hero section and see
- 36:57what we have. Right now, we have been
- 36:59working on the header component, which
- 37:01is actually just the left part of the
- 37:03hero content section. Or if you're
- 37:05looking at it on a mobile device, that
- 37:07is this top part. But now is the time to
- 37:10fill in this huge gap on the right side.
- 37:13And we can do that by introducing this
- 37:15great 3D room model. So, let's do that
- 37:19in the next
- 37:21lesson. to start adding our great 3D
- 37:24model right below or to the right side
- 37:26of the text. We can head back over into
- 37:29the hero section and we'll implement it
- 37:32right here below the header where it
- 37:35says right side 3D model. I wouldn't
- 37:37even call this a model. It's actually an
- 37:39entire 3D
- 37:41experience. So, let's go ahead and
- 37:43create this component. Right within
- 37:46source components, create a new folder
- 37:49and call it hero models. Within hero
- 37:52models, create a new file and call it
- 37:55hero
- 37:58experience.jsx. Just run rafce for now.
- 38:02Later on, we'll implement the entire
- 38:03experience here. But for now, import it
- 38:06right here where it says 3D model by
- 38:09putting it in a figure. Figure is an
- 38:12HTML 5 semantic tag that says that
- 38:14something important will be right here
- 38:17and it is self-contained meaning it is a
- 38:19single unit that we want to display
- 38:20within it. We can render a div that'll
- 38:23have a class name equal to hero-3d
- 38:28layout. This will give it 70% of the
- 38:30width of the screen on large devices or
- 38:33full width on smaller devices. So, if I
- 38:35head back right here and give this a
- 38:37border red of 200 and
- 38:41border-2, you should be able to see that
- 38:43it takes full width of the screen. But
- 38:45if we expand it, it's taking about 70%
- 38:48of the screen right here, which is still
- 38:50a lot to show this model. So, right
- 38:52within it, let's render this
- 38:54self-closing hero experience. This
- 38:57component is absolutely positioned. So,
- 38:59you can see it jumps a bit over here,
- 39:01but as we implement it, it's going to
- 39:03jump right below. So let's head over
- 39:05into it. And here we have to render our
- 39:07GB model. So what is a GB model? Well, a
- 39:11GB is a file format that allows us to
- 39:14render 3D scenes and models. You can
- 39:17find many of these models on websites
- 39:19like SketchFab. Some of these are free
- 39:22and some of them are paid and you can
- 39:24easily download them and put them right
- 39:26within your application. But let's take
- 39:28it slow. First things first, I want to
- 39:30set up a canvas because in 3JS
- 39:33everything begins with a canvas. So
- 39:35instead of a div, let me render a canvas
- 39:38which I have to import from React 3
- 39:41fiber. And we have to have a camera
- 39:43within that canvas. So I'll say camera
- 39:46is equal to and then we can set the
- 39:49position of the camera to an array of
- 39:51something like zero on the x-axis, zero
- 39:54on the y-axis, and about 15 on the
- 39:57z-axis, which is pulled back a bit,
- 39:59allowing us to frame the model nicely.
- 40:01We can also give it something known as
- 40:02an FOV or field of view of 45, allowing
- 40:06us to once again nicely frame it within
- 40:09our camera. And of course, we cannot yet
- 40:10see anything if we don't have a light.
- 40:12So let me add an
- 40:14ambient light which is a self-closing
- 40:17component with an intensity of something
- 40:20like 0.2. And we can also add a color to
- 40:23this light such as hash 1 a1 a40. If you
- 40:28save this, you won't be able to see
- 40:31anything just yet. Why? Well, because
- 40:34there's nothing to see. There's just
- 40:36light. So to see something within the
- 40:39canvas, you need to add a 3D object such
- 40:43as a mesh with a geometry and a
- 40:45material. Well, here's an example of
- 40:48adding a simple box. You can create a
- 40:51new mesh and then within it, you can
- 40:53render a box
- 40:56geometry with args an array of 11 one,
- 41:01which is how wide and how big it is. And
- 41:05then you can also give this mesh some
- 41:06kind of material such as mesh standard
- 41:10material and make it a color of
- 41:13something like
- 41:16blue. If you do this and save, you still
- 41:19won't be able to see it. And that's
- 41:21because we have to appropriately light
- 41:23our subject. Ambient light in this case
- 41:25is not enough. So, let me add a
- 41:28directional light with a position of
- 41:31something like five five and an
- 41:36intensity of one. If you do that, you
- 41:38should be able to see a blue or let's
- 41:41make it teal
- 41:43box. Pretty cool, right? But right now,
- 41:46it's just a 2D box. So, how can we
- 41:49actually move it around to see it in all
- 41:51of its three dimensions? Well, we can
- 41:53add something known as orbit controls,
- 41:56allowing us to move around this 3D
- 41:59model. I'll add it right here above the
- 42:02mesh by calling it orbit controls coming
- 42:05from React 3D. It is a self-closing
- 42:08component. And if you just call it like
- 42:10this, you'll already be able to move
- 42:12this box in all different dimensions.
- 42:15And you can even zoom it in. As you can
- 42:17see, currently only some sides are lit
- 42:19up while the others are not. You can
- 42:22play with the intensity of the light to
- 42:23see how that changes the model. And of
- 42:26course, this is something we dive much
- 42:27deeper into within our vanilla 3JS
- 42:29course. So, I just had to show this to
- 42:31you. Uh we go super deep into different
- 42:34types of lights that there are. And then
- 42:38check this out. You can see exactly what
- 42:40each 3JS property is doing right here
- 42:43within the lesson. I mean, just how cool
- 42:47is that? You can play with the intensity
- 42:49of different point lights, change the
- 42:52positions of the lights, and even change
- 42:54the rotations. We have a second point
- 42:56light. You can see how it gets reflected
- 42:58on different elements. Pretty cool
- 43:00stuff, right? And right now we are
- 43:02dealing with a directional light. We
- 43:04dive a bit deeper into the power
- 43:06property. And check this out. At the
- 43:08end, you can also practice your
- 43:10knowledge using a quiz. Pretty cool,
- 43:12right? And if something's not clear, you
- 43:14can ask questions right here within the
- 43:16comment system. and I'll reply very
- 43:18soon. But with that said, let's actually
- 43:20make something out of this cube. Or
- 43:22rather, should I say, we need to import
- 43:24our real 3D model here. But before,
- 43:27let's first play a bit with the orbit
- 43:29controls. I will set the enable
- 43:33pan to be set to false, which is going
- 43:36to prevent panning the scene, so you can
- 43:39only zoom in and zoom out, as well as
- 43:41move around. Also, I want to enable the
- 43:44zoom only if we're on desktop devices.
- 43:47But if we're on tablet, then I want to
- 43:49disable it because it's going to prevent
- 43:51the users to be able to scroll
- 43:52throughout the website. So I'll create a
- 43:55dummy variable called is tablet and I'll
- 43:59make it equal to false for now. So here
- 44:02we can say if not is tablet then enable
- 44:06zoom else disable it. Of course very
- 44:09soon we'll have to make this work for
- 44:11real but for now we're good. After that,
- 44:13we can define the max distance that we
- 44:16can zoom out. I'll set it to 20. As well
- 44:20as min
- 44:22distance, and I'll set that to about
- 44:24five. We can also set the min polar
- 44:27angle to
- 44:29math.pi over 5, and a max polar angle to
- 44:34math.pi over two. This allows us to
- 44:37control where in the screen we can move.
- 44:40For example, we can move up to this
- 44:42point up. We cannot have a full view
- 44:45down and a full view up in the room. We
- 44:48can only look at it from this angle. And
- 44:50that's exactly what these polar angles
- 44:52are doing. While we're here, let's also
- 44:54implement this is tablet functionality
- 44:56for real. And for that, we'll use a
- 44:59library called React Responsive. So,
- 45:03I'll simply run mpmi
- 45:06react-responsive to install it. And this
- 45:08will allow us to ensure that on larger
- 45:10devices, users can rotate and zoom while
- 45:13mobile and tablet users will get a bit
- 45:15of a simpler experience. Using this
- 45:18library is super simple. You just have
- 45:20to say is tablet is equal to use media
- 45:24query and then you pass a query you want
- 45:27to validate for such as max width of
- 45:32768 pixels like this. And then for the
- 45:36other one we can maybe define is mobile.
- 45:38Is mobile will be max width about yeah I
- 45:42think 768 and actually tablet is 124
- 45:46like this. So now we have this real is
- 45:49tablet variable. But now is the time
- 45:51that instead of this fake cube which you
- 45:54can see now we cannot zoom on mobile
- 45:56devices which allows us to then move up
- 45:58and down normally as we would on any
- 46:00other website. But if I zoom in on
- 46:03desktop, that still works. So now the
- 46:05main goal is that instead of this cube,
- 46:08we actually render this full 3D model.
- 46:11While I was creating the design for this
- 46:13amazing 3D portfolio, I was thinking for
- 46:16a long time, which model should we use.
- 46:20I went through many different
- 46:22iterations, but ultimately ended up on
- 46:24this one. It is a developer-like room.
- 46:29It has a lamp, a PC, a robot vacuum, a
- 46:33chair, and two screens. Pretty nice.
- 46:36It's also not super realistic. It has a
- 46:38nice artistic look. A bit of gloom from
- 46:41these monitors shining away. And it just
- 46:44feels like a developer's room, right,
- 46:47which you want to put on your portfolio.
- 46:49But of course, you're completely free to
- 46:51search any 3D model you want. Sketch Fab
- 46:54is one of the better websites. So, for
- 46:57example, you can search for a laptop or
- 47:00a MacBook model. There we go. And there
- 47:03are many cool ones that you can use and
- 47:05display in the hero of your application.
- 47:07But, as I said, I really like this one,
- 47:09and I think you will, too. So, let me
- 47:12show you what the process is of using
- 47:14some of these models in your apps. You
- 47:16could totally go ahead and download it
- 47:18right here. After you log in, it'll just
- 47:21give you a GB file and then you would be
- 47:24able to import it within your
- 47:25application. But here's the thing, some
- 47:28of these models are super large. For
- 47:30example, the GB file is 1 megabyte
- 47:33large. And even though that is not so
- 47:36big, it has 33,000 triangles, which
- 47:39makes it take some time to load. And
- 47:41that is without the lights that we need
- 47:43to display on it. So what I did is I
- 47:46didn't want to use this model directly.
- 47:48Rather, I asked my friend that knows a
- 47:51bit of Blender for 3D modeling to clean
- 47:54up unnecessary meshes, hitting
- 47:56geometries, and parts that we just
- 47:58didn't need on the screen. This allowed
- 48:01us to reduce the number of triangles
- 48:03while still preserving the original
- 48:05detail. And they also grouped some parts
- 48:07together, so it's easier for us to name
- 48:10them and manage them within the code.
- 48:12and she's actually teaching me those
- 48:14Blender optimization techniques so I can
- 48:16cover them within the Elite 3JS course
- 48:19which will release soon on the platform.
- 48:22But for now, just so you don't have to
- 48:24go through that process yourself, I
- 48:25decided to give you already optimized
- 48:28models right here within the public
- 48:30folder. So remember putting this here
- 48:32before, if you go into models, you'll
- 48:34see an optimized
- 48:36room.gb. This is the model you want to
- 48:39use. Now to be able to use any kind of a
- 48:41GB model, you need to first convert it
- 48:44to JSX by using the gltf JSX package. It
- 48:48is a very simple package that turns gltf
- 48:51files into JSX components. It is super
- 48:55simple to use. Let me show you. I'll
- 48:57open up my terminal and actually open up
- 48:59a new one. Within this one, I will cd
- 49:03into /public slmodels. So now I'm in the
- 49:08same folder where we have all of these
- 49:10models we'll end up using. The only
- 49:12thing you have to do is run mpxg
- 49:17gltfjsx optimized
- 49:21room.gb and press
- 49:24enter. It's asking you whether you want
- 49:26to install that package. So say yes or
- 49:29just y. And this will create a clean
- 49:32optimized room.jsx JSX file for you
- 49:35containing the scene hierarchy that we
- 49:37can directly import and render within
- 49:40our React 3 fiber project. Here it is
- 49:44optimized
- 49:45room.jsx. So simply move this file over
- 49:49to source components hero models and put
- 49:52it here right next to the hero
- 49:55experience. Then head over into the
- 49:57optimized room and rename the file to
- 50:01something like room. You can also change
- 50:03it right here at the top. Export
- 50:05function room. And this was
- 50:07autogenerated by gltf to
- 50:10JSX. Pretty cool, right? So what is
- 50:14this? Well, this is a JSX representation
- 50:16of this 3D model. It has different
- 50:19meshes and geometries that we can use
- 50:22and all of that together will form that
- 50:24room. Just make sure to point it to the
- 50:27right file. Currently, it's pointing to
- 50:29optimized room GB, but you'll have to
- 50:31point it to forward
- 50:34slashmodels/optimized roomg. And you'll
- 50:37have to prepend that word models right
- 50:39here as well because if you check out
- 50:42our file and folder structure, you'll
- 50:44see it's within public models and then
- 50:46optimize room. Great. Now, let's import
- 50:49this model into our hero experience.
- 50:52I'll head over to that component and
- 50:55instead of just rendering a mesh, a box
- 50:58geometry, we can render this room. So,
- 51:01I'll render a self-closing room
- 51:03component coming from components room.
- 51:06And if I save it, check this out. Even
- 51:10on mobile, you cannot really zoom in and
- 51:12out. And right now, it's not positioned
- 51:15in the best way possible, but you
- 51:18already get the idea for what this is.
- 51:21In my case, it is a heavily lit room,
- 51:23like there is a sun shining right here
- 51:26from the middle of the room. The more
- 51:29directional light intensity you add, the
- 51:31brighter it'll be and the less you'll
- 51:33see the colors. But if you reduce it,
- 51:36for
- 51:37example, already it'll start looking so
- 51:40much better. Now, what do you say that
- 51:42we more properly position this model on
- 51:45our screen? So instead of just rendering
- 51:48the room itself, I'll render it inside
- 51:50of a group which then allows us to
- 51:53adjust different things such as the
- 51:56scale of the room. So I can say that if
- 52:00we are on mobile devices then the scale
- 52:02will be 0.7 times the original size else
- 52:06it'll be the full size. So now the model
- 52:09will be a bit easier to handle on mobile
- 52:11devices. We can also change the
- 52:15position where we can make it 0 on the
- 52:18x-axis, minus 3.5 on yaxis and zero on
- 52:23the z-axis to move it down a bit. There
- 52:26we go. This makes more sense. And we can
- 52:28also change the rotation a tiny bit by
- 52:32making it equal to zero minus
- 52:36math.pi over 4 and then zero on the
- 52:40z-axis. This will rotate it slightly. So
- 52:43once you first open it up, it should
- 52:45look something like this. Nicely
- 52:47positioned within your mobile screen.
- 52:49Currently our lights are totally off,
- 52:51but don't worry, we're going to fix it
- 52:53soon. Before that, check it out on
- 52:55desktop. Looking great. Well, soon
- 52:58enough it'll look great. Lights truly do
- 53:01make a big difference when working with
- 53:033D models. So, let there be light.
- 53:06Instead of simply adding lights one by
- 53:08one like this, we can actually create a
- 53:11new component within the hero models.
- 53:14And I'll call it hero
- 53:18lights.jsx. Run rafce and then simply
- 53:21add the lights right within this empty
- 53:23react fragment. For now, I will simply
- 53:27use the same two lights that we had
- 53:29here. So if I copy them and remove them
- 53:31from here, save. Oh, just the screens
- 53:34will light. This is pretty cool because
- 53:36they emit their own light. Uh, but I'll
- 53:40put those right here, ambient
- 53:43directional. And now I will import the
- 53:46hero lights right above this group. And
- 53:49you can see we have light again. So now
- 53:53we can head into the hero lights and
- 53:55start implementing all the different
- 53:56lights that we want to have in our
- 53:58application. You might think that there
- 54:00are not many lights right here, but the
- 54:02more you think about it, the more lights
- 54:03you'll see. I'll increase the width of
- 54:06my browser a bit so we can see it a bit
- 54:08better. Let's start by removing those
- 54:10two lights and add a spot light just
- 54:14like this. Make sure to spell the L in
- 54:16spotlight
- 54:18uppercased. And let's give it a
- 54:20position. This one will be the front
- 54:23right spotlight, which is going to mimic
- 54:25a strong key light like the desk lamp
- 54:28that we have right here. So, I'll
- 54:30position it on two 56. And you might
- 54:33want to play with this a bit just to
- 54:35make sure to get it right. And let's of
- 54:37course give it some kind of
- 54:39intensity. I'll make it very strong,
- 54:41like 100. And now you can see that it's
- 54:44coming from the top right. But if you
- 54:46play with these values a bit, like 2
- 54:48to2, you'll see how strong it is right
- 54:51now. Or we can make it come from the
- 54:54center of the screen. 0
- 54:560. You can't see anything then. But then
- 54:59if you change it to 1
- 55:01one, you can see how well it is lighting
- 55:04everything. As you move the numbers a
- 55:06bit like 135 or maybe 145 or maybe you
- 55:11make it this two, you can see how this
- 55:13light behaves. For now, I'll set it to
- 55:18256. There we go. This is already
- 55:20looking so much better. We can also
- 55:24angle the spotlight a bit to maybe
- 55:26something like 0.15. And check this out.
- 55:29Get what I was saying? We are really
- 55:32talking about a desk lamp light right
- 55:35here. Yep. You can go to that level of
- 55:37detail where only a small part of the
- 55:41model is visible. There's also this
- 55:43interesting property called penubra,
- 55:45which basically means the softness
- 55:47around the edges of that light. And you
- 55:50can change the color of the light to
- 55:52something like
- 55:54white. There we go. Now let's add an
- 55:57overhead lamp. I'll copy this initial
- 55:59light. Change the position to four
- 56:0454. Change the angle to
- 56:070.3. The intensity to about 40. We don't
- 56:10need it to be that strong. Penubra to
- 56:12about 0.5. And we can give it a bit of a
- 56:16cool blue tone. Let's say that we have
- 56:18some colored LEDs. Something like 4 CC
- 56:220F0. And now we're lighting this larger
- 56:25part of the screen as well. Let's add
- 56:27another one. And I mean, you can play
- 56:28with this as much as you'd like. We
- 56:30could have been totally happy with what
- 56:32I had initially with just one
- 56:34directional light, but I want to really
- 56:36create the mood, if you know what I
- 56:38mean. Next one, we'll have minus
- 56:41three, five, and five. There we go. So
- 56:45now you can notice how these lights
- 56:47start overlapping. With an angle of 0.4,
- 56:50four. We can give it a super soft edge
- 56:53of just
- 56:54one intensity to about
- 56:5760 and a color of like
- 57:029D4 ed which is going to give it a bit
- 57:05of a different tone. And now I want to
- 57:07add another type of light.
- 57:10This one will be defined as primitive
- 57:12because within primitive you can provide
- 57:14any kind of an object here which will be
- 57:17a new three dot wreck area light. So
- 57:22this is a special type of light that
- 57:25provides a soft and moody fill light.
- 57:28You can also provide it a color like
- 57:30A259 FF. I found that one to work pretty
- 57:34well. Oh, and here I forgot to pass a
- 57:37hash. So the color wasn't actually
- 57:39getting applied. So if I comment out
- 57:41this primitive light just so you can see
- 57:43what was happening. This is looking so
- 57:45much more interesting than before.
- 57:47Right? So now we have this super white
- 57:49light. We have this bluish spotlight
- 57:52right here. And then we also have this
- 57:55purplish spotlight at the right side.
- 57:57And now we're adding this additional
- 58:00wrecked area light. To be able to use
- 58:02it, you have to first import at the
- 58:05top. So I'll say
- 58:08import asterisk as three from three. And
- 58:13this allows you to use different
- 58:15elements from the library. Of course,
- 58:17this lights up everything a bit too
- 58:20much, but you get what I'm going for,
- 58:22right? We can change its
- 58:25position to about 0 1 0. And we need to
- 58:30change this
- 58:32intensity to 15. And the rotation is the
- 58:35most important part. We want to angle it
- 58:37down toward the scene. So I'll change
- 58:40the
- 58:41rotation
- 58:43to minus
- 58:46math.pi over 4.
- 58:49Math.pi over 4 and then zero for the
- 58:52Z-axis. It's also important what else
- 58:55you pass into this rect area light. See
- 58:57the second property it takes in is the
- 59:00intensity and then the width and the
- 59:01height. So alongside the color I'm going
- 59:04to provide eight as the intensity to
- 59:07this wrecked area
- 59:09light, three as the width and two as the
- 59:12height. So that's going to angle it a
- 59:14bit more toward the room. Finally, I
- 59:17want to add a point light which is going
- 59:19to be very subtle and scattered for
- 59:22color balance. So I'll simply give it a
- 59:26position of 0 1 0. So, it's going to be
- 59:31on the center side, but a bit lower with
- 59:34an
- 59:35intensity of 10 and a color of hash
- 59:42720
- 59:449b7. I found that color to work very
- 59:47well. And I'll add another point light
- 59:50right below it by duplicating this one.
- 59:53This one will come from behind or off to
- 59:55the right. So that's going to be 1 2 and
- 59:59minus two with an intensity of 10 and a
- 1:00:03deep blue color like 0D 00 A4. So now
- 1:00:07the lighting in this room looks pretty
- 1:00:10crazy, doesn't it? I think one thing we
- 1:00:12can do to tone it down a bit is properly
- 1:00:15position this rectangular light by
- 1:00:18changing the position to come from the
- 1:00:20front right side.
- 1:00:22So on X-axis one, a bit from the top, so
- 1:00:26Y-axis three, and then four from the
- 1:00:30Z-axis. You can see this now fills the
- 1:00:32room a bit nicer. And maybe the most
- 1:00:35important thing that I missed is the
- 1:00:38softness around the edges of the light.
- 1:00:41Even though I thought I applied it, you
- 1:00:43can see that these lights seem to be
- 1:00:45fighting with each other, one
- 1:00:47overlapping with the other. And while
- 1:00:49it's pretty cool to see that you can
- 1:00:50test color theory by applying different
- 1:00:52colored lights over the same surface
- 1:00:54area, we don't want it in our
- 1:00:57application. So when I said penumbra
- 1:01:00here, I was actually missing an M. So if
- 1:01:02you add it here, you'll notice how this
- 1:01:04light will get much softer and it won't
- 1:01:06compete with the rest of the lights.
- 1:01:08Same thing for all the other two
- 1:01:10spotlights. Yeah, I think the glow of
- 1:01:13the lamp shows it the best because it
- 1:01:15has such a high intensity. Check this
- 1:01:18out. If I change the value of that
- 1:01:20property to nothing, so it doesn't
- 1:01:22exist, it'll have a very sharp light.
- 1:01:26But if I change the value, it'll soften
- 1:01:29it and more and more and more until it
- 1:01:33becomes barely noticeable, but it is
- 1:01:36still there. In this case, lamps really
- 1:01:38do emit some light, so I want to leave
- 1:01:41it right here. Pretty cool stuff, right?
- 1:01:44But now everything still seems a bit too
- 1:01:47purplish or bluish, doesn't it? It
- 1:01:50doesn't seem like a real room. Doesn't
- 1:01:53have that same artistic style that the
- 1:01:55original model had. So, let's go outside
- 1:01:57of the lights for a bit and let's go
- 1:01:59back to the room model to see whether we
- 1:02:02can fix some of these meshes, materials,
- 1:02:05and geometries. I want to replace some
- 1:02:08of these default materials with the ones
- 1:02:11we're going to custom define that better
- 1:02:13react to light. That way, we'll have
- 1:02:16more control. For example, a fong
- 1:02:19material, that's
- 1:02:21fng, is great for soft shading with
- 1:02:25subtle highlights, whereas the standard
- 1:02:27material is great for realistic lighting
- 1:02:30and reflections. So depending on what we
- 1:02:32want to achieve, we can apply a
- 1:02:34different material to different meshes.
- 1:02:37The way it works is like this. You can
- 1:02:40first import something known as use
- 1:02:43texture by saying const matcap texture
- 1:02:47is equal to use texture coming from
- 1:02:52react 3J. And here you can define where
- 1:02:55that texture is coming from. In this
- 1:02:58case, we can go to forward slash images
- 1:03:02slashextures
- 1:03:05slmmath1.png. And if you try to take a
- 1:03:08look at this math 1 image, you should be
- 1:03:10able to find it under images textures
- 1:03:13math 1. And it should look something
- 1:03:15like this. We'll use it for some meshes
- 1:03:17right here. And you'll soon see how
- 1:03:19that'll look like. So just to show you a
- 1:03:21simple example, I'll say const curtain
- 1:03:25material and make it equal to a new
- 1:03:293 mesh fong
- 1:03:32material. And to it you can pass a color
- 1:03:35of hash
- 1:03:39D90429 as an example. And don't forget
- 1:03:42to import everything as three from three
- 1:03:47to be able to use these materials. Now
- 1:03:51we can put that curtain material to use.
- 1:03:54For example, in this first mesh geometry
- 1:03:57where it says materials blend one, you
- 1:03:59can use the curtain material and see
- 1:04:02what difference does it make. Currently,
- 1:04:04we haven't applied it properly, which is
- 1:04:06actually good because you can see how
- 1:04:08this mesh looks like without any
- 1:04:11material. Super plain, right? But as
- 1:04:13soon as I properly fix this color right
- 1:04:15here by removing this space, you can see
- 1:04:18that we have a completely different
- 1:04:20story. We get a much more vibrant color
- 1:04:23that reacts to light a bit differently.
- 1:04:26So, this right here is the material that
- 1:04:28we had before, blind one. Okay, pretty
- 1:04:32cool, but a bit plain. And then this is
- 1:04:35our new curtain material, which is
- 1:04:37nothing special. It is just a basic
- 1:04:40built-in 3GS mesh fong material with a
- 1:04:43bright, vibrant color. And I know this
- 1:04:45might seem like a lot because we're
- 1:04:47diving into adding so many different
- 1:04:50sorts of lights to make this room super
- 1:04:54unique because let's be honest, light is
- 1:04:56what allows you to see the room in the
- 1:04:58first place. So, we tackled spotlights,
- 1:05:01primitives with the wrecked area lights,
- 1:05:04point lights, and more. And now we're
- 1:05:06even changing the original materials of
- 1:05:08some meshes right here. The one that's
- 1:05:10going to make the biggest difference for
- 1:05:12sure has to be this texture that we're
- 1:05:15importing from a PNG. Yep, you can
- 1:05:18import a texture from a PNG image. Let
- 1:05:22me show you how. I'll say const body
- 1:05:25material and make it equal to a new 3
- 1:05:29mesh fong material to which I will pass
- 1:05:34a map. So instead of a color you can
- 1:05:37pass a map and I'll pass it as the matte
- 1:05:40cap texture that I defined above from
- 1:05:43image. And now we can apply this body
- 1:05:46material right here to this second mesh.
- 1:05:49Instead of materials blend, I'll apply a
- 1:05:52body material and see if you can notice
- 1:05:54the difference. It's the walls. See how
- 1:05:57the walls react differently to lights
- 1:06:00depending on which materials we give it.
- 1:06:03This is cool, sure, but this definitely
- 1:06:06makes it look a bit different if that's
- 1:06:08what you're going for. Now, just so you
- 1:06:10don't have to go ahead and update every
- 1:06:12single material with its own color, it
- 1:06:14is like being a bit of an artist, right?
- 1:06:16Changing the colors. I'll provide you
- 1:06:18with the final room.jsx file right
- 1:06:21within the video kit. So head over here
- 1:06:24at the bottom of the file and copy it
- 1:06:27room.jsx. If you paste it, you'll notice
- 1:06:30that we're leaving some materials as
- 1:06:32blind. Some like the table we're
- 1:06:34changing and then some more like the
- 1:06:36radiator pillow material, maybe even
- 1:06:40something like the chair or the
- 1:06:41computer. That's what we need to change.
- 1:06:44So, if I save this now and reload, we
- 1:06:48get something that looks a bit more like
- 1:06:50this. It's much closer to the final
- 1:06:52effect that we were going for. I just
- 1:06:54feel that this works with the original
- 1:06:56theme of the portfolio that we're going
- 1:06:58for. Oh, and notice one cool thing, and
- 1:07:01that is that these monitors now have
- 1:07:03this some kind of a glow. And that in
- 1:07:06the world of 3JS is called a selective
- 1:07:10bloom. So, I can change the intensity of
- 1:07:12that bloom, and you'll notice how much
- 1:07:15stronger it'll be. It like the light
- 1:07:17bleeds out. So, I found the value of 1.5
- 1:07:21to work the best. So, it seems like
- 1:07:23they're shining too bright to be able to
- 1:07:25see what's on them. Now, if you reload
- 1:07:27the page on your desktop device, you'll
- 1:07:30see the full 3D experience. We have a
- 1:07:34what appears to be a super moody room
- 1:07:36where a lot of vibe coding happens. But
- 1:07:40once again, I invite you to play with
- 1:07:43the lights a bit. Every change that you
- 1:07:45make makes a big difference to the final
- 1:07:47model. And in the final app, I even
- 1:07:49added some particles. But to be honest,
- 1:07:52I think that's a bit too much because
- 1:07:55snow or particles don't really fall
- 1:07:58within a room, right? Uh but if you do
- 1:08:01want to add them, I'll leave it right at
- 1:08:02the bottom of the video kit. The only
- 1:08:04thing you have to do is create a
- 1:08:07new component within the hero models.
- 1:08:11Call it
- 1:08:14particles.jsx. Paste that code right
- 1:08:16here. And then get back over to your
- 1:08:19hero experience. And then right below
- 1:08:21the lights, you can render the
- 1:08:25particles and pass the count of how many
- 1:08:28particles you'd like. I'll set it to
- 1:08:30about 100 and see the magic
- 1:08:34happen. If you want to really freeze
- 1:08:36your computer, set it to a thousand and
- 1:08:39see what happens. There we go. That's a
- 1:08:42sky full of stars if I've ever seen one.
- 1:08:44Pretty cool, right? Again, either don't
- 1:08:46include it at all as it might be a bit
- 1:08:48of an overkill or leave it to about 100
- 1:08:51max. So, with that in mind, great job on
- 1:08:55completing the hero section. It has been
- 1:08:58the toughest part of this portfolio.
- 1:09:00Trust me on that. But at least you learn
- 1:09:02a lot about
- 1:09:06lights. Now, there is one more thing
- 1:09:08regarding this hero that you might have
- 1:09:09not noticed we're missing right now. As
- 1:09:11opposed to how it is in the final
- 1:09:13application. It is the loading animation
- 1:09:15of this text. Even though this part is
- 1:09:18always animating, once we reload, we
- 1:09:20also want to animate each one of these
- 1:09:22lines line by line to look something
- 1:09:25like this. Check this out. The first one
- 1:09:28will drop from the top first, then the
- 1:09:30second, and then the third. Check this
- 1:09:31out. As I reload the page, they all drop
- 1:09:34one by one from the bottom to the top.
- 1:09:38So, let me show you how to implement
- 1:09:39that animation. This time, we won't be
- 1:09:42using 3JS. Rather, we'll be using Gap.
- 1:09:45So head over into your hero component
- 1:09:48and let's
- 1:09:50import use GSAP coming from gap react as
- 1:09:56well as import gap which is the general
- 1:09:59library from gap just like this. Then
- 1:10:02inside of the component we can call the
- 1:10:05use gap hook to animate this h1 tags. We
- 1:10:09can do it by saying use gap to it. You
- 1:10:11have to pass a callback function that we
- 1:10:13want to activate. And there you can use
- 1:10:16the gap library to say go from and then
- 1:10:20go to. And to this you can pass two
- 1:10:23different objects to define what's
- 1:10:24happening with these elements. First you
- 1:10:27have to target which elements you want
- 1:10:29to animate. In this case I'll animate
- 1:10:31the hero text class name. So make sure
- 1:10:34to start with a dot. And specifically I
- 1:10:37want to animate all H1 elements within
- 1:10:39the hero text class name. Then we're
- 1:10:42going to provide a from position. So I
- 1:10:45want to animate it from these properties
- 1:10:48to the following properties. Let's say
- 1:10:50that I want the elements to start from a
- 1:10:53bit of a higher position on the Yaxis.
- 1:10:56For example, Y is 50 and opacity of
- 1:10:59zero. So we can bring them to life later
- 1:11:02on from zero opacity to the full
- 1:11:05opacity. Then we want to animate them to
- 1:11:08Y0. So we want to bring them down. We
- 1:11:11want to change the opacity to one. We
- 1:11:14want to stagger each one of these H1
- 1:11:17elements by 0.2 seconds. So we get first
- 1:11:21one 2 3. And I want to set the duration
- 1:11:24of the animation to 1 second. And the
- 1:11:28easing to power two dot inout. This is
- 1:11:32just how this specific animation will
- 1:11:34play out. So now if I save this and if I
- 1:11:37increase the duration to something like
- 1:11:385 seconds so you can see it better and
- 1:11:40reload you should be able to see how all
- 1:11:43of these three are coming one after
- 1:11:45another. To make the effect even more
- 1:11:47pronounced I can change the stagger to
- 1:11:50about 1.5 seconds. Reload. And check
- 1:11:54this out. First we have the first one.
- 1:11:57Then we have the second one. And then we
- 1:11:59have the third one. Of course you never
- 1:12:01want to keep your users waiting. So,
- 1:12:03what you can do is bring the stagger to
- 1:12:050.2 and the duration to 1 second.
- 1:12:08That'll end up with a nice animation
- 1:12:10that looks like this. Of course, the
- 1:12:11text is even larger on full screen, so
- 1:12:14the animation looks even better there.
- 1:12:16Looking good, right? Gap is not that
- 1:12:18hard. So, let me explain what happens
- 1:12:21here. The Y position moves each headline
- 1:12:24from 50 to zero to give it some
- 1:12:26movement. And then the opacity from 0 to
- 1:12:291 fades the element in. And then a
- 1:12:32stagger gives us that cascading
- 1:12:35animation. Pretty cool, right? Now,
- 1:12:37another thing that we need to do with
- 1:12:38our hero section is to make this button
- 1:12:41scroll a bit down. We cannot see the
- 1:12:43scroll just yet because we haven't
- 1:12:45implemented any additional sections, but
- 1:12:47once we do implement it, we want it to
- 1:12:49scroll very smoothly to a very precise
- 1:12:52position. This is what we want to
- 1:12:55achieve. I click it and we go right here
- 1:12:57to the top of these statistics. and then
- 1:13:00we can see the portfolio. So to
- 1:13:02implement that head over into the hero
- 1:13:05section and then from the hero head over
- 1:13:07to the button that we created that is
- 1:13:10right here within the header. There we
- 1:13:13go. Now to this button we have the
- 1:13:16anchor tag which is the main clickable
- 1:13:18element. So let's give it an on click
- 1:13:21function where we can access the click
- 1:13:24event within here. The first thing we
- 1:13:26want to do is prevent the default
- 1:13:28behavior of the browser and that is to
- 1:13:31reload the screen once we click on an
- 1:13:33element. Then we want to get access to a
- 1:13:36target that we want to scroll to. And
- 1:13:39you can use the
- 1:13:41document.getelement by ID. And whatever
- 1:13:44ID you define, we can scroll to it. In
- 1:13:47this case, I'll call it counter because
- 1:13:49right here within these elements, we
- 1:13:51want to count up some of these
- 1:13:53statistics. So I'll call it counter.
- 1:13:55Then we only want to scroll if we found
- 1:13:58that element and if we have an ID. In
- 1:14:00this case, we're only passing the ID
- 1:14:03from the hero section and we won't pass
- 1:14:06it later on once we use it in the
- 1:14:07contact section. So the contact should
- 1:14:09send the email and not scroll up. Only
- 1:14:12this one should scroll up. Next, we have
- 1:14:14to give it a bit of an offset, which
- 1:14:16means kind of figure out to leave some
- 1:14:18space at the top. So I'll say const
- 1:14:21offset is equal to window.in inner
- 1:14:23height and I found the number 0.15 to
- 1:14:27work pretty cool. I'll show you later on
- 1:14:29how it works with and without this.
- 1:14:31Finally, we want to calculate how far
- 1:14:34down we need to scroll. So we'll say top
- 1:14:36is equal to target dot get bounding
- 1:14:41clientrect dot top plus the windows
- 1:14:46scroll
- 1:14:47y minus the offset because we want to
- 1:14:51leave some space on top of these
- 1:14:53elements to make them appear nicely on
- 1:14:55the screen. And finally, you can say
- 1:14:57window.croll to define the top that you
- 1:15:00want to scroll to and set the scroll
- 1:15:03behavior to smooth. So very soon as we
- 1:15:07implement those counters right here
- 1:15:09below, we'll be able to click and scroll
- 1:15:12down. So let's implement it. I'll head
- 1:15:15back over to my hero section.
- 1:15:18And below this figure and below the div
- 1:15:21that's wrapping it, still within the
- 1:15:23section, I'll render an animated
- 1:15:25counter. But let's first create that
- 1:15:27component within the components folder
- 1:15:30and call it animated
- 1:15:35counter.jsx. Run rafce right within it.
- 1:15:38And then import it right here where we
- 1:15:40were. Animated counter, a self-closing
- 1:15:44component.
- 1:15:45And if you do that, you should be able
- 1:15:47to see it right here at the bottom. I'll
- 1:15:49collapse my browser. Head into the
- 1:15:51animated counter and we can implement
- 1:15:53it. We can start with the JSX. I'll
- 1:15:57first wrap everything in a div that has
- 1:15:59an id of counter because we're going to
- 1:16:02use this element to scroll to later on.
- 1:16:04Next, I want to give it a class name
- 1:16:07equal to
- 1:16:09padding-x-lg to give them some space on
- 1:16:11left and right. I also want to give it
- 1:16:14on extra-large devices a margin top of
- 1:16:16zero, but typically a margin top of 32.
- 1:16:20Within this div, we can have yet another
- 1:16:22div that'll have a class name equal to
- 1:16:27margin x of auto grid of four columns.
- 1:16:32So, we're going to show four different
- 1:16:33cards within it. We can map over our
- 1:16:36counter items. So, just start typing
- 1:16:39counter items and press enter. It will
- 1:16:42automatically import them from your
- 1:16:44constants. Here I put different values.
- 1:16:48For example, years of experience,
- 1:16:50satisfied clients, completed projects,
- 1:16:52and client retention rate. You can of
- 1:16:55course change this if you maybe have 10
- 1:16:57years of experience or less. You most
- 1:16:59likely have much less, but that's
- 1:17:00totally okay. Satisfied clients, you can
- 1:17:03change all of these numbers or values as
- 1:17:07well as the labels that you want to
- 1:17:08display right here. It's all dynamic.
- 1:17:11So let's map over those counter items by
- 1:17:14saying that map where you get each item
- 1:17:17and for each item you want to
- 1:17:19automatically return a
- 1:17:22div that'll look a bit like this. It'll
- 1:17:26have a key equal to counter items label.
- 1:17:31And you want to give it a class name
- 1:17:33equal to counter dash number
- 1:17:37text-white text-5 XL
- 1:17:41font-bold and margin bottom of two.
- 1:17:43Next, you can give it a zero number. So,
- 1:17:47we're going to start all of them with a
- 1:17:48zero. And then you can render the
- 1:17:51item suffix. What is this suffix? Well,
- 1:17:55it is either a plus for a number or a
- 1:17:57percentage for the retention rate. And
- 1:17:59then if you save this, you should
- 1:18:01already be able to see four different
- 1:18:02numbers right here. But let's actually
- 1:18:05wrap this in another div. So I'll make
- 1:18:08it a div like this. And I'll put it
- 1:18:11right within it. We want to wrap it in a
- 1:18:14card first. So this outer div will have
- 1:18:17a class name of bg zinc of
- 1:18:21900
- 1:18:23rounded-lg padding of 10 flex flex- call
- 1:18:28and a justify center. So now we can see
- 1:18:31four of these different cards just
- 1:18:33showcasing empty values. But before the
- 1:18:36value we can actually render another div
- 1:18:40that will render the item label. So, we
- 1:18:43have zero years of experience, zero
- 1:18:45satisfied clients, and zero completed
- 1:18:47projects. Not so good. Let's give this
- 1:18:50div a class name of
- 1:18:55text-w. Okay, that's a bit better. But
- 1:18:58still, why are we saying zero right
- 1:19:01here? Well, that's because we always
- 1:19:03want to start with a zero, but then we
- 1:19:06want to count the elements up to the
- 1:19:08value that we have chosen. either 10,
- 1:19:11100 or more. Sure, what you could do is
- 1:19:14immediately display it right here. So
- 1:19:16that's going to be just item value. And
- 1:19:19that's pretty cool already, right? But I
- 1:19:22want to show you how you can animate
- 1:19:24this element to go up in number to reach
- 1:19:27those specific values. So let's do that.
- 1:19:30Next, to implement our counter, we can
- 1:19:32use a react countupub mpm package. You
- 1:19:36can simply install it by saying mpm
- 1:19:38install react
- 1:19:42countub and then we can use it by
- 1:19:44importing the countupub right at the top
- 1:19:48and then using it like a countup
- 1:19:50component that'll look something like
- 1:19:52this. Within this div, we can implement
- 1:19:55a count up and give it a start and an
- 1:19:58end. If you don't provide a start, it'll
- 1:20:00start from zero. And then right here as
- 1:20:03the end we can implement the item dot
- 1:20:06value. And you can also add a prefix or
- 1:20:09a suffix to it. So the suffix can be
- 1:20:13item.suffix. Let's see how that looks
- 1:20:15like. I'll delete this. Save it. And if
- 1:20:18I go back and reload, you can now see
- 1:20:21that all of these numbers count up. Even
- 1:20:24though this is 10 and this is 100, the
- 1:20:26component automatically takes enough
- 1:20:28time to load them all the same.
- 1:20:31Perfect. This is looking amazing. And
- 1:20:34let's check it out on desktop. Yep, this
- 1:20:37is looking good. We have our hero
- 1:20:39section which takes full height of the
- 1:20:41screen. Very soon we can also add a
- 1:20:44navbar right here. But for now, we have
- 1:20:47this hero and then if you click on see
- 1:20:49my work, it actually scrolls down to
- 1:20:52these countup cards. So this is looking
- 1:20:55great. And on mobile, the reason why we
- 1:20:57had to put so much work into the see my
- 1:21:00work button is because sometimes you
- 1:21:01cannot scroll on mobile devices as
- 1:21:04instead you'll be actually drag and
- 1:21:06dropping our 3D model. So it was super
- 1:21:08important for this see my work to
- 1:21:10properly scroll down to the countup
- 1:21:12section. So with that in mind, we have
- 1:21:15now implemented the hero section with
- 1:21:17this amazing 3D model and GEP animations
- 1:21:20we have added to these H1s as well as
- 1:21:22implemented a counter for some of the
- 1:21:25most important metrics about our years
- 1:21:27of experience, satisfied clients,
- 1:21:29projects, and more. In the next lesson,
- 1:21:32let's focus on developing this showcase
- 1:21:35section where you can display your
- 1:21:37highlighted project alongside two other
- 1:21:39projects you've done
- 1:21:41before. With the hero section done,
- 1:21:44let's focus on the section below, the
- 1:21:46showcase section, where we show three
- 1:21:49real world apps you've built, one large
- 1:21:52featured app on the left, and two
- 1:21:54supporting projects on the right. First
- 1:21:56things first, we have to create a new
- 1:21:58section. So within the sections folder,
- 1:22:00create a new file and call it showcase
- 1:22:06section.jsx, run
- 1:22:08rafce and imported right within the app
- 1:22:11below the hero showcase section right
- 1:22:15here. If you do that, you'll be able to
- 1:22:17see a showcase section text right below.
- 1:22:20So let's get started with the layout.
- 1:22:22First, I'll wrap everything with a div
- 1:22:24and I'll give it an ID equal to work so
- 1:22:27that we can use the navigation bar links
- 1:22:29later on to scroll to that section. Then
- 1:22:31I'll give it a class name of app
- 1:22:34dashcase. And this will nicely center it
- 1:22:37on the screen. Right within it, I'll
- 1:22:39create another div. And this one will
- 1:22:42have a class name equal to w- full. So
- 1:22:45it takes the full width of the screen.
- 1:22:47And then within it, I'll have another
- 1:22:49div with a class name equal to showcase
- 1:22:54layout. This one we'll make it flex row
- 1:22:57on extra-l large devices. So we can fit
- 1:22:59one on the left and two on the right.
- 1:23:00And flex call so all of them show one
- 1:23:03below another on mobile devices. So now
- 1:23:05we can split this into two sides. We're
- 1:23:08going to have the left side right here
- 1:23:10on the top and then after that we'll
- 1:23:12have the right side. So on the left side
- 1:23:15let's create a new div. This div will
- 1:23:17have a class name equal to first project
- 1:23:22wrapper. And within it, I'll create
- 1:23:24another div with a class name of
- 1:23:28image-wrapper. And within it, I'll
- 1:23:30render a self-closing image with a
- 1:23:32source of forward/
- 1:23:36images/roject
- 1:23:381.png. And I'll give it an al tag of
- 1:23:41ride because if you take a look at my
- 1:23:44images right here within the public
- 1:23:46folder images and then we have our
- 1:23:49project one you'll see that this is ride
- 1:23:52a react native Uberlike application
- 1:23:55which if you want to learn how to build
- 1:23:57well you can do it for free on YouTube.
- 1:23:59If you wanted to put the image or the
- 1:24:01thumbnail of your project, which you
- 1:24:03most likely want to do, unless you build
- 1:24:06this one, of course, then you'll have to
- 1:24:07upload your own image to the images
- 1:24:09folder and just refer to it right here.
- 1:24:12And then you'll be able to see it right
- 1:24:13at the bottom. Within this div
- 1:24:15containing the image, I'll create
- 1:24:17another div with a class name equal to
- 1:24:21text-content. And within it, I'll
- 1:24:24display an
- 1:24:25H2 that'll say
- 1:24:27ondemand rides made simple with a
- 1:24:32powerful
- 1:24:34userfriendly app called Ride. Of course,
- 1:24:38you can enter the title of your
- 1:24:40application right here. Then we can have
- 1:24:42another description paragraph right
- 1:24:44below with a class name of
- 1:24:49text-white-50 and on medium devices
- 1:24:51text- XL. And here I'll copy what I have
- 1:24:54in the already deployed website, an app
- 1:24:57built with React Native, Expo, Tailwind
- 1:24:59CSS for fast, user-friendly
- 1:25:02experiences. Of course, I invite you to
- 1:25:05talk a bit more about the technologies
- 1:25:06you used here or what obstacles you
- 1:25:09encountered while developing it. And now
- 1:25:11we can head over below this div for the
- 1:25:14left side and focus on the right side.
- 1:25:17There we can have one div which will
- 1:25:19wrap those two additional projects with
- 1:25:21a class name of project list wrapper and
- 1:25:27overflow-hidden. Within it we can have a
- 1:25:29div for our first project. So let's give
- 1:25:32it a class name of project. And within
- 1:25:34it we can render a div within which we
- 1:25:37can render that project's image. So
- 1:25:40let's give it a source of images project
- 1:25:442.png with an alt tag of library
- 1:25:47management platform. If you save it,
- 1:25:50you'll see one image right here. But we
- 1:25:53can position it a bit better by styling
- 1:25:55this div that's wrapping it with a class
- 1:25:57name of
- 1:26:00image-wrapper bg of hash
- 1:26:05ffb just like this. So we nicely
- 1:26:07position it within the screen. Right
- 1:26:09below this div containing the image, we
- 1:26:11can also render an H2 where we can
- 1:26:13simply say library management platform.
- 1:26:16In this case, we don't need to give it a
- 1:26:18description because this is a secondary
- 1:26:19project. So let's go ahead and copy this
- 1:26:22entire project div. So for me, that's
- 1:26:24this red one right here. And I'll paste
- 1:26:27it one more time just below this time
- 1:26:30for our second project. So I'll change
- 1:26:32the background color a bit so they're
- 1:26:34not the same to
- 1:26:37FF7 EB. There we go. So just a bit of a
- 1:26:40different color. Image will point to
- 1:26:42project 3. And the AL tag will say YC
- 1:26:47directory. This is yet another of our
- 1:26:49YouTube
- 1:26:50projects. YC directory which is a
- 1:26:54startup
- 1:26:56showcase app. Of course, I invite you to
- 1:27:00put your own projects right here. And
- 1:27:02now is the time that we add some
- 1:27:04animations to this screen. It doesn't
- 1:27:07look that exciting on mobile, but on
- 1:27:09desktop, we do have a big project in the
- 1:27:12left and two smaller ones on the right.
- 1:27:14But if you compare it with the final
- 1:27:15project, you'll notice that there they
- 1:27:18nicely appear one after another. Whereas
- 1:27:21on our current application, they just
- 1:27:23show up and that's it. So let's animate
- 1:27:26them one by one. To do that, you'll want
- 1:27:29to give each one of these project divs
- 1:27:31its own reference. So right at the top,
- 1:27:34let's create a new const section ref is
- 1:27:38equal to use ref coming from react. And
- 1:27:42at the start, set it to be equal to
- 1:27:44null. We need one for the section. And
- 1:27:46then we need one for each one of the
- 1:27:47projects. So I'll say con project one
- 1:27:50ref is equal to use
- 1:27:53ref at the start set to null again and
- 1:27:57then I'll duplicate it two more times
- 1:27:59for the project 2 and project 3. Now we
- 1:28:03can give a ref to the main section. So
- 1:28:05I'll actually rename this to an HTML 5
- 1:28:08semantic section tag with a ref of
- 1:28:12section ref. And then I'll also give it
- 1:28:14to each one of these projects. The first
- 1:28:16one being this first project wrapper.
- 1:28:19I'll give it a ref equal to project one
- 1:28:23ref. And then I'll head over to this
- 1:28:26second project and I'll give it a ref of
- 1:28:28project 2 ref. And finally for the third
- 1:28:32one I'll give it a ref of project 3 ref.
- 1:28:36So now all of these different divs or
- 1:28:39projects have different references
- 1:28:41attached to them. So now we can apply
- 1:28:44some animations on them. Let me show you
- 1:28:46how to do that using GSAP. First things
- 1:28:48first, we will import GSAP within curly
- 1:28:52braces from GSAP. And the one thing that
- 1:28:55you want to do is say
- 1:28:58GSAP.register plugin. And you'll pass in
- 1:29:01a scroll trigger which you'll need to
- 1:29:04import from Gap. So say import in curly
- 1:29:07braces scroll trigger coming from gap
- 1:29:11scroll
- 1:29:12trigger and finally we'll also need to
- 1:29:15import the use gap hook which we used
- 1:29:17before which is coming from at gap
- 1:29:20react. You need to register this plugin
- 1:29:24because otherwise gap won't know that
- 1:29:26you want to apply some scroll related
- 1:29:28features. You already know how to start
- 1:29:30applying gap animations. You do it using
- 1:29:33the use gap hook. So let's say use gap
- 1:29:37right here. And you have to give it a
- 1:29:39callback function with a dependency
- 1:29:41array right here at the bottom. It works
- 1:29:44similarly to React use
- 1:29:46effect. This means that everything in
- 1:29:49this block of code runs only once when
- 1:29:51the component mounts. So there's one
- 1:29:53thing that I want to do first and that
- 1:29:56is fade in the entire section that shows
- 1:29:59these different projects. I can do that
- 1:30:02by using the section
- 1:30:05ref.curren and I want to animate it from
- 1:30:07and to something. So I'll actually have
- 1:30:10to say gap from to and then the first
- 1:30:14parameter you pass to it is the
- 1:30:16reference to a div that you want to
- 1:30:18animate. Then you pass from which
- 1:30:21animations you want to go. So I want to
- 1:30:23go from opacity of zero. And then you
- 1:30:26for the third parameter pass where you
- 1:30:28want to go. You want to go to opacity of
- 1:30:31one throughout the duration of 1.5
- 1:30:36seconds. And I'll actually put this into
- 1:30:38multiple lines so it's easier to see
- 1:30:40what's
- 1:30:41happening. So gap from two apply to this
- 1:30:45div opacity from zero to 1 throughout
- 1:30:49the duration of 1.5 seconds. If you do
- 1:30:52that and reload, you'll notice that now
- 1:30:55it fades in pretty nicely. And now we
- 1:30:57want to animate each project on scroll
- 1:31:00one by one. We can do that by gathering
- 1:31:03them into an array and then loop through
- 1:31:06each one and create a scroll animation.
- 1:31:09So right at the top I'll say const
- 1:31:11projects and create an array of those
- 1:31:14projects. Then to it I want to add a
- 1:31:18project one
- 1:31:20ref.curren, project two ref.curren
- 1:31:24current and finally project 3
- 1:31:27ref.curren. So we have stored all of
- 1:31:29these references in an
- 1:31:31array. Then we want to say
- 1:31:35cards. So for each card and take its
- 1:31:38card
- 1:31:39index open up a new function
- 1:31:42block and then run the gap from to
- 1:31:48function. What do you want to animate?
- 1:31:50Well, I want to animate a card. Which
- 1:31:52card? Well, the one that we're going
- 1:31:54over for that specific round of the for
- 1:31:57each loop. And what do I want to do to
- 1:31:59it? I want to start it at
- 1:32:02Y50 and opacity of zero, which means
- 1:32:05that it starts as
- 1:32:07invisible. And then what I want to do to
- 1:32:10it is I want to change the Y to zero,
- 1:32:13which means that we're going to move it
- 1:32:14back to its normal position and make it
- 1:32:17fully visible by changing the opacity to
- 1:32:19one throughout a duration of 1 second.
- 1:32:22And I also want to add a delay to
- 1:32:25stagger when they load. So I'll say 0.3
- 1:32:29seconds and then take the index plus
- 1:32:32one. That's because the indices start at
- 1:32:35zero. So instead of having 0 to 1, we'll
- 1:32:37have 1 * 0.3. So the delay for the first
- 1:32:40one will be 0.3. Then for the next one,
- 1:32:43it'll increase to 0.6. And then it'll
- 1:32:46increase one more time to 0.9. And
- 1:32:49finally, to know when we want to apply
- 1:32:50this scroll animation, we have to have a
- 1:32:53scroll trigger. Here you can say when
- 1:32:56you reach which portion of the screen,
- 1:32:58when do they want to trigger it? And we
- 1:33:00can say the trigger is actually the
- 1:33:02card. So animate when the card enters
- 1:33:05the viewport. And then you can decide
- 1:33:07when exactly you want to start it. For
- 1:33:09example, start when the top of the card
- 1:33:12is near the bottom of the screen. So
- 1:33:15about 100 pixels from the bottom. And
- 1:33:18right here, I should have said projects.
- 1:33:20So we're mapping over each one of these
- 1:33:22project cards. Okay. So now if you
- 1:33:25reload and scroll down, well, you won't
- 1:33:29be able to see the animation just yet.
- 1:33:30You'll just be able to see this one
- 1:33:32where it goes from opacity zero to
- 1:33:34opacity 1. So why cannot we see this
- 1:33:37animation? Is it something with the for
- 1:33:40each loop? Well, not really. It's just
- 1:33:43that you have to use Gap from within the
- 1:33:46use GSAB hook. So, simply select this
- 1:33:49and bring it down into the use GUB hook
- 1:33:53right
- 1:33:54here. There we go. And you can also
- 1:33:56bring these projects here. So, now we're
- 1:33:59saying use Gap and then we're applying
- 1:34:02the GSAP animation. So, if I now save
- 1:34:05this, scroll up to the top, reload, and
- 1:34:09then scroll down, you can now see how
- 1:34:11each one of these nicely appears as you
- 1:34:14scroll. And of course, let's not forget
- 1:34:16that this looks even better on desktop
- 1:34:18devices. So, as you scroll, we get all
- 1:34:20of them appear nicely one by one. Great.
- 1:34:24And that brings us to the end of the
- 1:34:26showcase section. Now, we could focus on
- 1:34:29the next section, but now that we have
- 1:34:31the hero and one more section, what we
- 1:34:34can focus on instead is implementing the
- 1:34:36navigation bar because now we have two
- 1:34:38different things that we can scroll from
- 1:34:40and to. So, let's do that.
- 1:34:43Next, let's get started with developing
- 1:34:46our navbar. It should look something
- 1:34:48like this on desktop where if you scroll
- 1:34:51down, you can always get back to the top
- 1:34:53of it by clicking your name. And then
- 1:34:55here it allows you to smoothly scroll to
- 1:34:58different parts of your portfolio. And
- 1:35:01then on top we have contact which brings
- 1:35:03us all the way to the bottom to this
- 1:35:06great 3D model that we'll implement
- 1:35:08soon. And then if you check it out on
- 1:35:10mobile it'll be very simple. We don't
- 1:35:13need the viewer to necessarily see every
- 1:35:15single thing. If they want it they're
- 1:35:17going to scroll but then at the moment
- 1:35:18where they want to contact you they can
- 1:35:20just go here and do that immediately. So
- 1:35:23let's focus on implementing the knobbar.
- 1:35:26We can start by creating a new component
- 1:35:29right within source and components and
- 1:35:32create it right here by creating a new
- 1:35:37navbar.jsx file. Run rafce and just
- 1:35:41import it over in your
- 1:35:43app.jsx above the hero section. So
- 1:35:46that'll be the navbar like this. If you
- 1:35:49do that and go back to your website, you
- 1:35:51should be able to see it right here on
- 1:35:53the top and then we can implement it.
- 1:35:56Just head over into it, turn it into an
- 1:35:58HTML 5 semantic header tag and give it a
- 1:36:02class name equal to navbar and within it
- 1:36:06create a div that'll have a class name
- 1:36:09of
- 1:36:10inner and within it render an anchor tag
- 1:36:13that'll have a class name of logo and an
- 1:36:17href of hash hero pointing to your hero
- 1:36:21section when clicked. And there you can
- 1:36:24type your name. I'll type Adrian
- 1:36:27JSM. Of course, you can put your first
- 1:36:29and last name right there. There we go.
- 1:36:33Pretty cool. Right now, right below this
- 1:36:35anchor tag, we can render nav for the
- 1:36:38rest of the navigation items. And here,
- 1:36:41I'll give it a class name of desktop.
- 1:36:44So, we know that whatever is within this
- 1:36:46nav only appears on desktop. But then
- 1:36:49after the nav, I'll render another
- 1:36:52anchor tag that'll have an href pointing
- 1:36:56to hash contact with a class name of
- 1:37:01contact-bn and group. And within it, we
- 1:37:05can render a div with a class name of
- 1:37:08inner. And within it, we can render a
- 1:37:11span that'll simply say contact me.
- 1:37:15There we go. So now we have a contact as
- 1:37:17well as AdrianJSM right here at the top.
- 1:37:21Now the question is what do we show
- 1:37:22within the nav? Well, for that we'll
- 1:37:25have to head over to the desktop version
- 1:37:26of the application. So I'll expand it a
- 1:37:29bit and within it let's render a ul, an
- 1:37:33unordered list. Within the unordered
- 1:37:36list, we want to map over all of our
- 1:37:38link items. Sure, you could create an
- 1:37:40array of objects where you have a title
- 1:37:43for each one and then you also have a
- 1:37:45URL that you want to point to, but it's
- 1:37:48always better to declare those links in
- 1:37:51your constants in case they change. You
- 1:37:53don't have to change them here. So,
- 1:37:55simply say nav links and import it over
- 1:37:58from constants index.js. If you want to
- 1:38:01change those links, you can, but these
- 1:38:04are the sections that our portfolio will
- 1:38:06have. work, experience, skills, and
- 1:38:09testimonials. Sections that every single
- 1:38:11portfolio needs to have. So once you're
- 1:38:14in there, just map over them, dstructure
- 1:38:17the link and the name that we get from
- 1:38:20each one of these links, and then for
- 1:38:22each one, automatically return a list
- 1:38:25item, an LI. And since we're mapping
- 1:38:28over it, we also have to provide it a
- 1:38:30key, which can be a name since it's
- 1:38:33unique with a class name of group. And
- 1:38:37within each one, we can render an anchor
- 1:38:39tag that'll point to the link. So this
- 1:38:43is a dynamic link property different for
- 1:38:45each one of these links. Within it, we
- 1:38:48can render a span element that'll render
- 1:38:51a name. And within it, we can also
- 1:38:53render another self-closing span that
- 1:38:56will have a class name of
- 1:38:59underline, allowing us to have that very
- 1:39:01cool visual effect. So if you save it,
- 1:39:04you should be able to see work,
- 1:39:06experience, skills, and testimonials
- 1:39:08with this cool underline. And if you
- 1:39:11click on work, since that is the first
- 1:39:13section we have implemented, you can
- 1:39:14immediately see it right here alongside
- 1:39:16its nice
- 1:39:18animation. Let's try it one more time. I
- 1:39:20can head up and I can see my work both
- 1:39:23here and here and it nicely animates.
- 1:39:27Pretty cool, right? Now, check this out.
- 1:39:30If we scroll down, you'll notice that
- 1:39:32our navbar will kind of be hidden by
- 1:39:34some of the other more colorful elements
- 1:39:36that we have on the screen. That means
- 1:39:39that we want to actually give it some
- 1:39:41kind of a background so that it's
- 1:39:42visible even on top of these elements.
- 1:39:45For that, we have to keep track of the
- 1:39:47state of the navbar. If we're here, just
- 1:39:50leave it like this. It has enough
- 1:39:52contrast with the background. But as
- 1:39:54soon as we scroll down, we'll then apply
- 1:39:57some kind of a background. To do that,
- 1:39:59we have to figure out when have we
- 1:40:00scrolled and when have we not. So right
- 1:40:04here at the top of the navbar, I'll say
- 1:40:06const and I'll define a new use state
- 1:40:10called scrolled and set scrolled. At the
- 1:40:13start, it'll be equal to the use state.
- 1:40:16That'll simply say false. And this use
- 1:40:19state of course is coming from React.
- 1:40:23Then I'll open up a new use effect
- 1:40:25coming from React that'll happen only at
- 1:40:28the start or when the component gets
- 1:40:30mounted. So I need to define the
- 1:40:33dependency array as an empty array. And
- 1:40:36within there we want to figure out how
- 1:40:38do we handle the scroll. So I'll create
- 1:40:41a handle scroll function which will
- 1:40:43check whether the user has scrolled at
- 1:40:46least 10 pixels. So I'll say const is
- 1:40:49scrolled is equal to
- 1:40:53windowscrolly is greater than 10. If
- 1:40:56that is the case I'll set scrolled to
- 1:40:58true because it means that the user has
- 1:41:00scrolled. And now I want to add this
- 1:41:03event listener to the window. It'll be a
- 1:41:05scroll event listener and it'll call the
- 1:41:08handle scroll function. And I also want
- 1:41:11to unmount it to clean it up. So I'll
- 1:41:14say return callback function
- 1:41:17window.ventlist listener scroll and
- 1:41:21handle scroll. So this way we're still
- 1:41:24keeping all of the optimization in React
- 1:41:26by removing or cleaning up the event
- 1:41:28listener when the component is
- 1:41:30unmounted. So now that we have access to
- 1:41:32this scroll state, let's put it to use.
- 1:41:35See this navbar? We want to apply
- 1:41:37different classes to it depending on
- 1:41:39whether the user has scrolled or not. So
- 1:41:42I'll make this class name dynamic and a
- 1:41:44dynamic template string for that matter
- 1:41:47it'll always have the class name of
- 1:41:48navbar but if scrolled is true in that
- 1:41:52case I'll also give it a class name of
- 1:41:54scrolled else I'll give it a class name
- 1:41:56of not scrolled. So if you check out
- 1:41:58both of these you can see that scrolled
- 1:42:00simply applies a top zero and a
- 1:42:03background black and then not scrolled
- 1:42:05applies a background transparent. That's
- 1:42:08it. That's the only difference. So now
- 1:42:10if you scroll down right here, you'll
- 1:42:13notice that now it has a dark
- 1:42:15background. So it's still fully visible
- 1:42:18even when appearing over all of these
- 1:42:19elements. And then if I go up, it simply
- 1:42:22disappears. Makes sense, right? And with
- 1:42:24that in mind, your navbar is now fully
- 1:42:27completed. It scrolls to work and to
- 1:42:30home, but later on it'll also scroll to
- 1:42:32all of these additional sections which
- 1:42:35we just about to implement.
- 1:42:37So let's start with the next one in the
- 1:42:39list and that one is the logo showcase
- 1:42:42that scrolls through the logos of
- 1:42:44different companies that you have worked
- 1:42:46with in the past. So let's do that
- 1:42:50next. Let's start implementing the logo
- 1:42:54carousel. This is all about social
- 1:42:57proof. You want to show off client logos
- 1:42:59in a smooth continuous scrolling. This
- 1:43:02is called a marquee, I think, just like
- 1:43:05the top tier agency sites do. So, let's
- 1:43:07create a new component just for it. I'll
- 1:43:10head over into the components folder and
- 1:43:12create a new file called logo
- 1:43:17section.jsx within it. You know the
- 1:43:19drill. Run rafce and then import it
- 1:43:23right here. Logo section as a
- 1:43:26self-closing component right below the
- 1:43:30showcase section. Once you do that, you
- 1:43:32should be able to see an empty piece of
- 1:43:33text. So, let's head over into it and
- 1:43:37instead of this empty div, create a div
- 1:43:40that has a class name of on medium
- 1:43:43devices, margin Y of 20. So that's 20 on
- 1:43:47top and bottom, but typically margin Y
- 1:43:49of 10. So only on medium devices do we
- 1:43:52want to have that larger margin Y. And
- 1:43:55then also give it a position of
- 1:43:57relative. right within it. Create two
- 1:44:00separate self-closing divs. Both of
- 1:44:02which will have a class name equal to
- 1:44:05gradient edge and then duplicate it.
- 1:44:09You'll see very soon what that does, but
- 1:44:11essentially you can see how these logos
- 1:44:13disappear on the left and then they
- 1:44:15start appearing from the right as it
- 1:44:17moves. So right below create a new div
- 1:44:20that'll have a class name equal to
- 1:44:24marquee and give it a height of about
- 1:44:2752. Then right within it create another
- 1:44:30div that'll have a class name of marquee
- 1:44:34box with a medium devices of gap 12 in
- 1:44:38between the elements and typically a gap
- 1:44:40of five should do the trick. This
- 1:44:42marquee is the visible area that you see
- 1:44:44on the screen. But the marquee box will
- 1:44:47actually have twice the width and it'll
- 1:44:50animate indefinitely to the left. This
- 1:44:52is how you can apply animations. So
- 1:44:54let's research them a bit deeper. You
- 1:44:57can see that this marquee takes 100
- 1:44:59dynamic viewport width and we hide it.
- 1:45:02Okay, so this is what you can see on the
- 1:45:04screen. But this marquee box will
- 1:45:06actually have 200% width and then an
- 1:45:10overflow hidden. So, we're animating it
- 1:45:13outside of the screen. And we're
- 1:45:14applying a simple CSS animation of
- 1:45:17Marquee 60 seconds linear infinite.
- 1:45:21Believe it or not, that's all it takes.
- 1:45:23You can do a lot of stuff built right
- 1:45:25into CSS. So, the only thing remaining
- 1:45:27for us to do is to add the logos. We're
- 1:45:30going to loop through all of the logos
- 1:45:32twice so it feels infinite. First, we
- 1:45:34need to import the list of the logos.
- 1:45:37So, I'll say logo icons list coming from
- 1:45:40constants. And as you can see, I put
- 1:45:43about 11 logos right here. But feel free
- 1:45:46to remove them or feel free to add the
- 1:45:48logos of the companies that you worked
- 1:45:50for or that you collaborated with. And
- 1:45:53if you didn't work with any major
- 1:45:54companies that have known or
- 1:45:57good-looking logos, well, then you might
- 1:45:59just skip this section altogether. if
- 1:46:01you want to. The only thing you have to
- 1:46:03do is comment it out from here and then
- 1:46:05move over to the next section. With that
- 1:46:08said, I will map over it, get an icon
- 1:46:11for each one, and then simply display a
- 1:46:14new component for each one of these. A
- 1:46:17component called logo icon. So, let me
- 1:46:20define it right here since this is the
- 1:46:22only place where it'll be used. Logo
- 1:46:25icon. It is a self-closing component
- 1:46:28that simply accepts an icon and it
- 1:46:32returns a
- 1:46:33div with a class name of flex-ash none
- 1:46:38flex- center and a marquee
- 1:46:41item and it'll render an image of a
- 1:46:46source icon img path as well as an al
- 1:46:50tag of
- 1:46:52icon.name. So now we can loop over or
- 1:46:55map over those logo icons like this. We
- 1:47:00can give it a key equal to
- 1:47:03icon.name and then we can pass over the
- 1:47:05entire icon equal to icon to it. So now
- 1:47:08if you save this, you'll be able to see
- 1:47:11the logos. But what we have to do is
- 1:47:13actually duplicate it one more time. And
- 1:47:16we're doing this because this trick
- 1:47:18creates a duplicate stream of logos. So
- 1:47:21when the first one sets to scroll out,
- 1:47:23the second one continues seamlessly.
- 1:47:26Hopefully it makes sense. But yeah,
- 1:47:28believe it or not, that is it. The magic
- 1:47:30here lies in this animation CSS property
- 1:47:34where you apply a specific type of the
- 1:47:36animation. Choose the duration, choose
- 1:47:39whether it's linear or not, and then
- 1:47:41make it infinite and then that'll scroll
- 1:47:43the images from left to right. Pretty
- 1:47:45cool, right? Once again, I'm totally
- 1:47:47aware that you might not have hundreds
- 1:47:49of companies you worked for in the past.
- 1:47:51So, if that is the case, you can totally
- 1:47:53just comment this section out and focus
- 1:47:55on the next one to come. Because the
- 1:47:57next ones are cards that feature your
- 1:48:01strengths. To start implementing the
- 1:48:04featured cards, let's go ahead and
- 1:48:06create a new section within the sections
- 1:48:08folder. I'll call it feature
- 1:48:13cards.jsx. run rafce and simply import
- 1:48:17it within the app feature cards. Now
- 1:48:22that I look at it, the logo section is
- 1:48:24also technically a section and not a
- 1:48:26component. So, let me move it right here
- 1:48:28into the sections, but make sure to fix
- 1:48:31up these imports so that it's coming
- 1:48:33from sections instead of from
- 1:48:37components. Great. With that said, move
- 1:48:40over into the feature cards and let's
- 1:48:42start implementing them.
- 1:48:44The goal of these cards is to highlight
- 1:48:47your top strengths as a developer.
- 1:48:49Things like the quality, communication,
- 1:48:51and timely delivery of your projects.
- 1:48:54Each card is a compact summary of one of
- 1:48:58your core abilities using an icon, a
- 1:49:00title, and a description. So first let's
- 1:49:03create a layout by creating a div
- 1:49:07that'll have a class name of w full
- 1:49:12padding x of lg and within it create
- 1:49:15another div that'll have a class name of
- 1:49:19margin x auto and let's split it into a
- 1:49:22grid that has three columns. Now you
- 1:49:25want to map over your abilities coming
- 1:49:28from constants index jsx. These are some
- 1:49:32of the abilities that Chad GPT thinks
- 1:49:34are important, but I want you to go
- 1:49:37ahead and change all of this text right
- 1:49:39here because you are a special
- 1:49:41individual and you have your own
- 1:49:43strengths. So change the titles,
- 1:49:45descriptions, and if needed, image paths
- 1:49:48as well to showcase what you are capable
- 1:49:51of. Then we want to map over all of
- 1:49:54these different strengths. And for each
- 1:49:56one, we can dstructure the image path,
- 1:50:00the title, and the description. And then
- 1:50:03we can just map over them and
- 1:50:05automatically return them. For each one,
- 1:50:08I'll return a div. And for now, I'll
- 1:50:11make that div simply render the H3
- 1:50:15that'll say title. If you do that and go
- 1:50:19back to our current application, you'll
- 1:50:21see quality focus, reliable
- 1:50:23communication, and on-time
- 1:50:25delivery. Let's style it a bit different
- 1:50:27to make it a card. First, I'll give it a
- 1:50:30key since this is an element we're
- 1:50:32mapping over. I'll make it a title since
- 1:50:34the title is unique. And I'll give it a
- 1:50:37class name of card-border.
- 1:50:42I'll also give it a rounded dash XL
- 1:50:46which will make the corners rounded. A
- 1:50:48padding of eight to give it some
- 1:50:51space. Also
- 1:50:53flex flex- call and a gap of four in
- 1:50:57between the elements within the cards.
- 1:50:59Next, I'll create an inner div right
- 1:51:02here that'll have a class name equal to
- 1:51:06size of 14 flex items center justify-
- 1:51:12center and rounded- full. This is for
- 1:51:16the icon we're going to display. So, let
- 1:51:18me render a self-closing image tag with
- 1:51:21a source of img path and an al tag of
- 1:51:25title.
- 1:51:27If you save this, you should be able to
- 1:51:28see this icon appear, which already
- 1:51:31brings so much more life into it. And
- 1:51:34below it, we have this H3 to which we
- 1:51:37can give a class name of
- 1:51:39text-wide
- 1:51:42text-2XL font- semibold and margin top
- 1:51:45of
- 1:51:46two. There we go. Looking good. And
- 1:51:49below it, we can render a P tag with a
- 1:51:52class name of
- 1:51:55text-white-50 and
- 1:51:58text-LG. And it can render the
- 1:52:01description. There we go. This is
- 1:52:04already looking so much better. One
- 1:52:06thing that I noticed is that right here,
- 1:52:08I misspelled padding. So, this was
- 1:52:10supposed to say padding
- 1:52:13XLG. And that'll provide some padding on
- 1:52:15left and right. And to give it some more
- 1:52:18vertical space to breathe, we need to
- 1:52:20rename this to grid three calls as in
- 1:52:23columns plural because this will apply
- 1:52:26this grid column layout which will
- 1:52:28nicely space them out on all different
- 1:52:30types of devices. There we go. This is
- 1:52:32looking better. Looks great on mobile
- 1:52:35and nicely complements this logo
- 1:52:37section. And if you check it out on
- 1:52:39desktop, it looks great too. Perfect. Oh
- 1:52:44yeah, I don't think I've seen the logo
- 1:52:45section on desktop. It looks even better
- 1:52:48because now you should get the idea of
- 1:52:50why we're using double the icons. So
- 1:52:52once some scroll to the left side of the
- 1:52:54screen, new ones appear on the right
- 1:52:56side. Perfect. This section is done as
- 1:53:00well. So now we can showcase not only
- 1:53:02the companies you worked for, but also
- 1:53:05your top features. But what matters even
- 1:53:08more than features is your experience.
- 1:53:11So, let's develop one of the most
- 1:53:13exciting sections of this portfolio
- 1:53:15next. Your work experience timeline. It
- 1:53:20has a couple of interesting elements
- 1:53:22such as this card right here that has
- 1:53:24this cool hover effect or this timeline
- 1:53:28that expands as you scroll down and
- 1:53:30changes colors depending on the logo of
- 1:53:33the company that you worked for. Pretty
- 1:53:35cool stuff, right? So, let's do that
- 1:53:38next.
- 1:53:41professional work experience. Or maybe
- 1:53:43it's not work experience for you, but
- 1:53:45rather you're showcasing the timeline of
- 1:53:47how you build different projects. Either
- 1:53:50way, this section will tell your career
- 1:53:53journey in a visually structured
- 1:53:55timeline, blending the clean layout with
- 1:53:58these animated transitions. On the left
- 1:54:01side, you'll have testimonial style
- 1:54:03experience cards. In the middle, we'll
- 1:54:05have a timeline, and on the right side,
- 1:54:08you'll have all of your job
- 1:54:09responsibilities. So, let's start with
- 1:54:11creating this section. I'll head over to
- 1:54:13the sections folder and create a new
- 1:54:16experience
- 1:54:20section.jsx. I'll run rafce and I'll
- 1:54:23simply import it over below the feature
- 1:54:26cards. There we go. Makes sense, right?
- 1:54:28It appears at the bottom. So, let me
- 1:54:30head over into it and turn it into a
- 1:54:33section. This section will have an ID of
- 1:54:36experience so we can nicely scroll to it
- 1:54:39from the navbar and it'll also have a
- 1:54:42class name of W full so it takes the
- 1:54:45full width of the screen as well as on
- 1:54:47medium devices a margin top of 40 to
- 1:54:50divide it from the top and typically a
- 1:54:52margin top of 20. It'll have section
- 1:54:56padding, so we give it some space to
- 1:54:58breathe. As well as on extra-l large
- 1:55:00devices, padding x will be zero because
- 1:55:03we have enough space to show it all
- 1:55:05around. Within this section, we'll have
- 1:55:07another div that'll have a class name
- 1:55:10equal to
- 1:55:12w-ful age- full on medium devices
- 1:55:15padding x of 20 and typically a padding
- 1:55:18x of five. Now within it, we want to
- 1:55:22display our header
- 1:55:24title. This is the title I'm talking
- 1:55:27about, my career overview, and then
- 1:55:29professional experience. We'll want to
- 1:55:31turn this into a new custom component as
- 1:55:34we will reuse it in multiple places
- 1:55:36later on. So let's do it by creating a
- 1:55:40new reusable component called
- 1:55:43title header.jsx.
- 1:55:47run rafce and import it over within the
- 1:55:50experience section title header just so
- 1:55:53we can very easily and immediately see
- 1:55:56it at the bottom. Then pass over a title
- 1:56:01equal to
- 1:56:02professional work
- 1:56:04experience and also pass a sub which is
- 1:56:08a subtitle that can say something like
- 1:56:10my career
- 1:56:13overview and maybe at the start of that
- 1:56:16sub we can also display some kind of an
- 1:56:18emoji maybe like a work briefcase or
- 1:56:21something like that. So now we can head
- 1:56:23over into that title header and we can
- 1:56:26accept those props and display them on
- 1:56:28the screen. So simply dstructure the
- 1:56:30title and the subtitle right at the top
- 1:56:34and return a div with a class name of
- 1:56:37flex flex- call items-c center and a gap
- 1:56:43of five. and within it render another
- 1:56:46div with a class name equal to
- 1:56:51hero-b within it render a p tag which
- 1:56:54will render the subtitle and therefore
- 1:56:57you'll be able to see this my career
- 1:56:59overview if you open it up within CSS
- 1:57:02you'll see that we're applying a black
- 1:57:04background border some padding Y on top
- 1:57:07and bottom and X for left and right we
- 1:57:10are making it rounded making it fit the
- 1:57:12screen making the text a bit small and
- 1:57:15making it not wrap so it stays in the
- 1:57:17single line. And then right below this
- 1:57:20div, I'll create another div with a
- 1:57:23class name of font semibold on medium
- 1:57:27devices text 5 XL typically text 3 XL as
- 1:57:32well as text center. And here we can
- 1:57:35display that big title headline such as
- 1:57:39this one professional work experience.
- 1:57:41So now that we have the title for our
- 1:57:44experience section, let's loop over our
- 1:57:47experience cards. We can do it right
- 1:57:49here below the title by creating another
- 1:57:53div with a class name of margin top of
- 1:57:5732 to divide it a bit from the top and a
- 1:58:00class name of
- 1:58:01relative. Within it, I want to create a
- 1:58:04div that will act as a wrapper for our
- 1:58:06experience cards. So, let's also give it
- 1:58:09a class name of relative, a zindex of
- 1:58:1250, and an extra-large devices space Y
- 1:58:16of 32, and space Y of 10 on all other
- 1:58:21devices within it. We want to map over
- 1:58:23our exp cards or experience cards coming
- 1:58:28from constants
- 1:58:30index.js. You can see here that I
- 1:58:32created three different cards. Each one
- 1:58:35is an object that has a review, which is
- 1:58:38just like some kind of a description of
- 1:58:40what I did for that company, an image
- 1:58:42path, a logo path, a title, date, and
- 1:58:47responsibilities. I think this gives you
- 1:58:49a pretty good idea of how you should
- 1:58:51fill in this data yourself for your
- 1:58:53previous positions. Once you do that,
- 1:58:55you'll need to map over it. So, let's
- 1:58:57say map. And for each one of these
- 1:59:00cards, we want to automatically return a
- 1:59:02div. Since we're mapping over it, this
- 1:59:05div will have a key equal to
- 1:59:08card.itle since it's unique and a class
- 1:59:12name of exp card wrapper and within it
- 1:59:16for now just display a card.title so we
- 1:59:19can see something. There we go. I worked
- 1:59:22as a React Native full stack and
- 1:59:25front-end developer. And now we want to
- 1:59:27make it look a bit nicer. So, let's
- 1:59:29first focus on this experience card
- 1:59:32right here that allows you to hover over
- 1:59:35these elements and then you get this
- 1:59:37nice looking border. Now, you can also
- 1:59:40see that we'll reuse this same card
- 1:59:43later on in customer feedback highlights
- 1:59:46or testimonials from specific people.
- 1:59:48So, of course, that tells you that this
- 1:59:49will be a reusable
- 1:59:51component. So, let's create it. I'll
- 1:59:54head over to the components folder and
- 1:59:57create a new card. I'll call it
- 2:00:01glowcard.gsx because we have the glowing
- 2:00:03border. I'll run rafce. And within the
- 2:00:07experience section right here, instead
- 2:00:09of rendering the title, we can render a
- 2:00:12div that'll have a class name equal to
- 2:00:17on extra-large devices w2 over6. So this
- 2:00:21will give it a specific width on larger
- 2:00:24devices. And then within it, we want to
- 2:00:26render a glow card. This will be a
- 2:00:29component that is not a self-closing
- 2:00:31component, meaning something goes into
- 2:00:34it. And what is that? Well, that can be
- 2:00:37a div with a self-closing image tag
- 2:00:41that'll have a source equal to
- 2:00:44card.gpath with an al tag of card. And
- 2:00:49then alongside passing that as the
- 2:00:51children into the glow card, we can also
- 2:00:53pass the card as a prop which will
- 2:00:56contain all of the other card
- 2:00:57properties. So let's head over into the
- 2:00:59card and let's accept all of these
- 2:01:01properties that we're passing into it.
- 2:01:04First we get the card itself, but of
- 2:01:07course we need to dstructure it. And
- 2:01:10then we get the children, which refers
- 2:01:12to everything that we passed over as a
- 2:01:15child of that component. So here we can
- 2:01:18return a div that'll have a class name
- 2:01:22equal to
- 2:01:24card-border
- 2:01:26timeline-card rounded- excel and a
- 2:01:30padding of 10. Within it, I'll display a
- 2:01:33self-closing div with a class name of
- 2:01:36glow. And now you can slowly start
- 2:01:38seeing how this card will glow as you
- 2:01:41hover over it. So let's try to look a
- 2:01:44bit deeper into how this glow works.
- 2:01:46Well, first of all, we remove the
- 2:01:47pointer events so you don't get the
- 2:01:49cursor pointer. So, it doesn't seem the
- 2:01:50cards are clickable because they are
- 2:01:52not. We position it absolutely over that
- 2:01:56element with full width and height in
- 2:01:59the middle of the screen. And then we're
- 2:02:00focusing only on changing the border of
- 2:02:03that card as we move to it. The code for
- 2:02:05the exact border is right here. And I am
- 2:02:08applying a bit of a more complex
- 2:02:10gradient right here. I found this CSS
- 2:02:13somewhere on the internet. It could have
- 2:02:14been a really nice code pen and I saved
- 2:02:17it somewhere and then I ended up using
- 2:02:19it on this project. Whenever you find
- 2:02:21some nice CSS, always save it. You never
- 2:02:24know when it's going to come in handy.
- 2:02:26So, now that we have this glow right
- 2:02:28here, we actually want to display
- 2:02:29something within this card. First, we
- 2:02:32want to display the number of stars or
- 2:02:34the rating that this person has given
- 2:02:36you. So let's do that within this div
- 2:02:39where I will open up a dynamic block of
- 2:02:42code and say array
- 2:02:45from length is
- 2:02:48five and then we want to get access to
- 2:02:51the index. So we're mapping over these
- 2:02:53five elements. We don't care about the
- 2:02:55first one. We only care about the index.
- 2:02:58So you do it like this. And for each
- 2:03:00one, you want to automatically return an
- 2:03:02image that'll be equal to a source
- 2:03:05images
- 2:03:08star.png. It has to have a key of index
- 2:03:11and it has to have an al tag of star and
- 2:03:15a class name equal to size of five. If
- 2:03:19you do this, you should be able to see
- 2:03:21five stars for each one of these cards.
- 2:03:24We can also style the div a bit further
- 2:03:26by giving it a class name of flex items
- 2:03:31center gap of one in between the stars
- 2:03:34and a margin bottom of five. So now we
- 2:03:38have five stars within each one of these
- 2:03:40cards. We can go below this div that's
- 2:03:43wrapping those stars and create another
- 2:03:45div with a class name of margin bottom
- 2:03:49of five. And within it, we can render a
- 2:03:52piece of text that'll render the review
- 2:03:55of the card. We can give it a class name
- 2:03:59equal to
- 2:04:00text-white 50 and
- 2:04:03text-LG. So, we make it a bit larger.
- 2:04:06And more importantly, right below this
- 2:04:08div, we can render the children. In this
- 2:04:12case, the children being the logo that
- 2:04:14we're passing over right here from the
- 2:04:17experience section. But now if you hover
- 2:04:19over it, just one side or one border
- 2:04:23actually lights up and you can see this
- 2:04:26gradient that is being applied to the
- 2:04:28top and bottom side of this
- 2:04:30border. But now how do we actually make
- 2:04:33it follow our cursor? Well, for that
- 2:04:36we'll have to do some math or rather
- 2:04:38we'll have to know about the position of
- 2:04:40our cursor. So let me show you how to do
- 2:04:43that. First, I'll create a new
- 2:04:47reference to an element and I'll call it
- 2:04:49card refs and I'll make it equal to use
- 2:04:53ref. And at the start, it'll be equal to
- 2:04:56an empty array of
- 2:04:58references. Then I'll create a function
- 2:05:01called handle mouse move. It'll be a
- 2:05:06higher order function which means that
- 2:05:08first we want to figure out over which
- 2:05:10card are we moving over and then we want
- 2:05:13to get access to the event of the actual
- 2:05:16mouse. So that move event and then once
- 2:05:19we have that we can figure out which
- 2:05:21card we're on. So we'll say const card
- 2:05:24is equal to card
- 2:05:28refscurren and get the index of the
- 2:05:30current card we're on. And if a car
- 2:05:32doesn't exist, simply exit out of this
- 2:05:34function. Now, of course, for this to
- 2:05:36work, we have to give this div a
- 2:05:40reference. And this reference will
- 2:05:43actually take in the element and it'll
- 2:05:46say card
- 2:05:51refs.curren index is equal to the
- 2:05:54element. So depending on which one we're
- 2:05:56currently on, we will only move for that
- 2:06:00specific card. And I'll also call the on
- 2:06:02mouse move. And I'll call the handle
- 2:06:06mouse move function and pass in the
- 2:06:08specific index that we are moving over.
- 2:06:12Make sure that we also have an index
- 2:06:13here. So we have to pass an index as a
- 2:06:16prop to this function. So I'll say
- 2:06:19index. And from the experience section,
- 2:06:22I'll also pass over the index next to
- 2:06:25the card.
- 2:06:27this index you have to get over right
- 2:06:30here as the second parameter of the map
- 2:06:33function. Great. So now that we have
- 2:06:35that, as you scroll down, you'll be able
- 2:06:37to see that no animation is happening.
- 2:06:40We're not yet tracking anything right
- 2:06:42now. We just know on which card we're
- 2:06:44on. So now the magic happens. First, we
- 2:06:48have to get the mouse position relative
- 2:06:52to the card. So first let's get access
- 2:06:54to the position of the card itself by
- 2:06:56saying wctck is equal to
- 2:06:59card.get
- 2:07:01bounding client rect like this. Then we
- 2:07:06have to get the x position of the mouse
- 2:07:09by saying e dot
- 2:07:12clientx minus
- 2:07:14rect.ft minus rect.width divided by two.
- 2:07:19I told you we'll have to do some math.
- 2:07:21And we want to do the same for mouse y
- 2:07:23position which is e.clienty y minus
- 2:07:28wctop minus
- 2:07:32rect.height over two. Not only do we
- 2:07:35have to calculate the position of the
- 2:07:37mouse but we also have to calculate the
- 2:07:40angle. So I'll say calc the angle from
- 2:07:44the center of the card. So I'll say let
- 2:07:47angle is equal to math.
- 2:07:51ATAN2. This 2 is a static method that
- 2:07:54returns the angle in the plane in
- 2:07:57radians between the positive x-axis and
- 2:08:00the ray from 0 0. Yep, I told you this
- 2:08:03requires some complex math, but I really
- 2:08:05wanted to show you how to do this cool
- 2:08:06effect on these
- 2:08:08cards. So to this you pass the mouse Y
- 2:08:12and then the mouse X positions. Then you
- 2:08:16multiply it over with 180° divided by
- 2:08:21math.pi. Then we need to adjust the
- 2:08:23angle so it's in between 0 and 360. So
- 2:08:27I'll say angle is equal to in
- 2:08:30parenthesis angle plus 360 modulo
- 2:08:34operator. So this will give us the
- 2:08:36remainder of the division 360. And trust
- 2:08:40me I asked Chad GPT to help a bit with
- 2:08:42this. So, no worries if it's not fully
- 2:08:44understandable on the first try. You can
- 2:08:46even copy this block of code and ask
- 2:08:48Chad GPT to explain it even further if
- 2:08:51my explanations are not enough. And then
- 2:08:54finally, we want to set the angle as a
- 2:08:57CSS variable by saying
- 2:09:00carstyle
- 2:09:02setpropy d-start and set it to angle +
- 2:09:0660. So this way we have nice movement
- 2:09:09over our cards. And before we test it
- 2:09:11out, looks like I made a mistake here. I
- 2:09:13should have said if no card then return.
- 2:09:16But if we do have a card, well then
- 2:09:18let's do all of this math and let's make
- 2:09:20it work. So now check this out. You can
- 2:09:23actually hover over this and depending
- 2:09:26on the angle that your mouse is pointing
- 2:09:29from the center of the card, it'll
- 2:09:32actually change. So if you very
- 2:09:34erratically move it around the center,
- 2:09:35you can see how it moves. But as you go
- 2:09:38to the outside, the angle becomes
- 2:09:40smoother and then you can move very
- 2:09:43smoothly to different sides. Again, this
- 2:09:45is a very cool effect. And do you need
- 2:09:48to know this as a full stack developer?
- 2:09:51Not necessarily. Nowadays, you can find
- 2:09:53a lot of these cool effects online. But
- 2:09:56if you want to become an amazing CSS or
- 2:09:58front-end developer that delivers these
- 2:10:01kinds of exceptional crazy experiences
- 2:10:05and if this interests you then
- 2:10:07definitely go for it. Try to understand
- 2:10:09on a deeper level how this gives us the
- 2:10:11angle how we're applying that angle over
- 2:10:14to a CSS property and then finally how
- 2:10:18once that property gets applied it
- 2:10:20actually calculates the gradient that
- 2:10:22we're applying to the border of this
- 2:10:24card. But with that said, this is
- 2:10:26looking great. And we could have totally
- 2:10:29stopped here with the professional work
- 2:10:31experience, but I want to dive much
- 2:10:34deeper. I want to implement the full
- 2:10:36history of your work, not just the
- 2:10:38testimonial. So, let's do that next. But
- 2:10:41first, it looks like I'm not passing the
- 2:10:43proper border right here. So, it's card
- 2:10:46and card border. There we go. So, now it
- 2:10:50makes it look like a card. And this
- 2:10:52makes the effect even better. And of
- 2:10:54course, don't forget that we're only
- 2:10:56halfway or maybe one/ird done with the
- 2:10:59professional work experience section.
- 2:11:01That's because this is only the left
- 2:11:04side. On mobile, it seems like we've
- 2:11:06done everything, but on desktop, well,
- 2:11:09we still have to add the timeline. And
- 2:11:11then to the right side, we have to add
- 2:11:13the full explanation of the experience.
- 2:11:16So, let's do that next. Back in the
- 2:11:18experience section, right below our glow
- 2:11:21card. So still within the same div right
- 2:11:24below the glow card and below one more
- 2:11:26div we can create another
- 2:11:29div. This div will have a class name is
- 2:11:33equal to on extra large devices w is 4
- 2:11:37over 6. So we have essentially split the
- 2:11:40screen into the first part which takes
- 2:11:4326 of the screen and the second one
- 2:11:45takes 46. Basically that's 1/3 and 2/3.
- 2:11:49So within this div we can create another
- 2:11:51div with a class name equal to flex and
- 2:11:58items-start. And within it we can do
- 2:12:00another div that'll have a class name
- 2:12:05equal to
- 2:12:07timeline-wrapper within which we can
- 2:12:10render a self-closing div that'll have a
- 2:12:13class name of
- 2:12:16timeline. And then below that we can
- 2:12:19create another div self-closing that'll
- 2:12:22have a class name of gradient dash line
- 2:12:27w1 and h dash full. If you save this you
- 2:12:32won't see anything yet and that's
- 2:12:33because we have to animate this timeline
- 2:12:36with gap. So for now let's instead focus
- 2:12:39on the second part of our screen which
- 2:12:42are going to be the experience cards.
- 2:12:44So, I'll head only one div below where
- 2:12:47we have this timeline wrapper. And I'll
- 2:12:50render another div that'll have a class
- 2:12:54name equal to exp text flex. On
- 2:13:00extra-large devices, a larger gap of 20.
- 2:13:04On medium devices, a gap of 10. And
- 2:13:07usually a gap of five with a position
- 2:13:09relative and zindex of 20 within it. We
- 2:13:13can then display another div that'll be
- 2:13:15a wrapper for the image. So I'll give it
- 2:13:18a class name of timeline- logo and
- 2:13:21within it I'll render an image which
- 2:13:24will have a source of card. Logo path
- 2:13:28with an al tag of logo. So now we can
- 2:13:31only see docker right here and the other
- 2:13:34two are not yet there because we need to
- 2:13:36fix up this gap animation of the
- 2:13:38timeline. Next, we can go one div below
- 2:13:41and create a div for the final card for
- 2:13:44our experience section. It'll have an H1
- 2:13:48right in between it. And this H1 will
- 2:13:51render the
- 2:13:52card.title. There we go. Full stack
- 2:13:55developer. We can make it a bit larger
- 2:13:57by giving it a class name of font-
- 2:14:00semibold and text-3 Excel. So now if I
- 2:14:04collapse this just a tiny bit, you
- 2:14:06should be able to see it right here.
- 2:14:08There we go. Below it, I'll display a P
- 2:14:11tag that'll have a class name equal to
- 2:14:16margin Y of five and text white of 50.
- 2:14:20And there we can render the car date.
- 2:14:24There we go. So now it says when you
- 2:14:26work there, but we can also add some
- 2:14:28kind of an emoji right here and maybe
- 2:14:31make it a calendar so we can show the
- 2:14:33date. But let's not use the current
- 2:14:35date. Let's use the generic
- 2:14:39calendar. There we go. Now we can do one
- 2:14:42more P tag below this one that'll have a
- 2:14:46class name of text dash #
- 2:14:51839 CB5. So, this is a color that I like
- 2:14:55for this one. And let's make it italic.
- 2:14:58And it'll simply say responsibilities.
- 2:15:01There we go. And now let's actually say
- 2:15:04what we did there. So below the p tag
- 2:15:07I'll render a ul with a class name of
- 2:15:11list
- 2:15:13d-disk ms of five margin top of five
- 2:15:18flex flex- call a gap of five and text
- 2:15:23dash white of 50. Within there we can
- 2:15:27map over card
- 2:15:30responsibilities.m mapap where we get
- 2:15:32each individual
- 2:15:34responsibility and we can now show it as
- 2:15:38a list item. So this list item will have
- 2:15:41a key equal to
- 2:15:43responsibility a class name equal to
- 2:15:47text-lg and finally it'll just render
- 2:15:50the responsibility.
- 2:15:53for example, developed and maintained
- 2:15:55userfacing features for the hosting
- 2:15:57website, collaborated with UIUX
- 2:15:59designers, and optimized web
- 2:16:01applications for maximum speed. Uh,
- 2:16:04these are all AI generated, don't look
- 2:16:07that good, but here's my quick tip for
- 2:16:10how you can write these in a better way.
- 2:16:12This is basically what you have
- 2:16:13accomplished. First, use strong action
- 2:16:16verbs. Developed, optimized,
- 2:16:19implemented, right? collaborated,
- 2:16:22achieved for example for specific
- 2:16:24performance, right? So start with a
- 2:16:26strong action verb led, work,
- 2:16:28contributed, you get the idea. But then
- 2:16:31bring in some tangible metrics or
- 2:16:34achievable results. So instead of just
- 2:16:36being generic with collaborated closely
- 2:16:39or developed userfacing features, tell
- 2:16:41me how much performance have you
- 2:16:43improved. For example, improved app
- 2:16:45performance and then try to be specific
- 2:16:48even down to a percentage or a number.
- 2:16:50improved front-facing app performance by
- 2:16:5417%. Or maybe improve the load speeds by
- 2:16:58two seconds. Whatever you do, try to be
- 2:17:00specific in what you did as that
- 2:17:03improves the credibility. And now that
- 2:17:04we have the left side and the right
- 2:17:06side, it's time to animate the timeline
- 2:17:09which is going to give it that final
- 2:17:11touch. So let's focus on implementing
- 2:17:13that using GSA. We'll start at the top
- 2:17:16of the screen. So let's start
- 2:17:18implementing this gap scroll animation.
- 2:17:21Uh we already have done a gap scroll
- 2:17:23animation when we were building these
- 2:17:26cards right here. So you know that if
- 2:17:28you want to use a scroll animation, you
- 2:17:30have to import a scroll trigger from
- 2:17:33Gap. So right here I'll say import the
- 2:17:37original GSAP library coming from GSAP.
- 2:17:41I'll also want to import the use gap
- 2:17:44hook coming from GSAP React. And
- 2:17:47finally, we want to get access to the
- 2:17:50scroll trigger, which will allow the
- 2:17:52scroll animations coming from GAP scroll
- 2:17:55trigger. And then you want to register
- 2:17:57it by saying gap.register plugin scroll
- 2:18:01trigger. There we go. And now we are
- 2:18:04ready to use this use gap hook right
- 2:18:07here. So let's say use
- 2:18:10gap. It takes in a callback function and
- 2:18:14an empty dependency array as a typical
- 2:18:17React use effect. Great. So what kind of
- 2:18:21animations do we want to implement here?
- 2:18:23Well, first we want to reveal the left
- 2:18:26side cards by sliding them in from the
- 2:18:29left and fading them in. See these
- 2:18:32cards? We want to slide and fade them
- 2:18:35in. If I show you the finished
- 2:18:37application and I reload, check this
- 2:18:40out. As I scroll down, you'll notice
- 2:18:43these cards slide in. There was the
- 2:18:46first one, second one, and third one.
- 2:18:49So, let's implement that. I'll say
- 2:18:52gap. We want to animate a card. So,
- 2:18:55where is this card coming from? Well,
- 2:18:58even though we don't have a card on its
- 2:19:00own here, we can use a gap utility.
- 2:19:04Check this out. I'll say
- 2:19:07gap.utils dot to array and we can take a
- 2:19:11class name called a timeline card and we
- 2:19:16can turn it into an array of cards and
- 2:19:18then we can map over each one of these
- 2:19:21cards and get access to that card
- 2:19:23element.
- 2:19:25Then once we actually have access to the
- 2:19:28card, we can say get this card from
- 2:19:32these specific animations or this
- 2:19:34specific state. First, we want to move
- 2:19:37the card in from the left side. So I'll
- 2:19:40say
- 2:19:42X% is equal to minus
- 2:19:45100. The opacity will also start from
- 2:19:49zero. And we want to set the origin of
- 2:19:52the animation to the left side. So I'll
- 2:19:55say transform origin will be left and
- 2:20:00left. I also want to add a duration of 1
- 2:20:04second. And ease which is a curve of how
- 2:20:08we're going to animate it or the speed
- 2:20:09of the animation. So I'll use the power
- 2:20:12two.in out. It starts slowly and then it
- 2:20:16speeds up. And finally, we want to
- 2:20:19trigger the animation on scroll. So I'll
- 2:20:22say scroll
- 2:20:24trigger. Oh, but first this ease has to
- 2:20:27be a string like
- 2:20:28this. And I can trigger it on the card.
- 2:20:33But when are we going to start? Well,
- 2:20:35we're going to start the animation when
- 2:20:38the card is 80% down the screen. So I'll
- 2:20:41say start top 80%. If you now save this
- 2:20:46and reload, you'll see how nicely it
- 2:20:49appears once you reach the bottom part
- 2:20:51of the screen. And then also another one
- 2:20:54and another one. You don't want to show
- 2:20:56it too soon because the person is still
- 2:20:58viewing the top part. But as you're
- 2:21:00scrolling down, you want to show it just
- 2:21:02as it hits the screen. So you see
- 2:21:04something happening. There we go. Now,
- 2:21:06we want to animate the timeline. As the
- 2:21:09user scrolls, we want to move from top
- 2:21:11and then go down. So in this case I'll
- 2:21:14use another gap
- 2:21:17functionality. I'll say
- 2:21:20gap.2 timeline. So that is the class
- 2:21:23name we're switching. And then we want
- 2:21:25to set the transform origin to bottom
- 2:21:29bottom which means that it can also
- 2:21:31start from the bottom. So see as I
- 2:21:33scroll up it goes away. And if I scroll
- 2:21:36down it appears. I'll choose an ease in
- 2:21:40this case a power one and out. and add a
- 2:21:43scroll
- 2:21:44trigger where we want to trigger it on
- 2:21:47the dot
- 2:21:48timeline when the start is at top
- 2:21:53center and we want to end it when we
- 2:21:56reach 70% down the screen. I'm going to
- 2:21:59be honest, I didn't come up with these
- 2:22:01values immediately. Coming up with the
- 2:22:03specific values takes time and testing.
- 2:22:06though I might maybe set it to 50 and
- 2:22:08play around a bit and see how it behaves
- 2:22:11and then update it later on. Also, we
- 2:22:14want to update the animation as the user
- 2:22:16scrolls. So, I'll say on update and this
- 2:22:19still goes within the scroll trigger.
- 2:22:21We'll call a function that takes a look
- 2:22:23at the self current animation or current
- 2:22:26progress and it scales the timeline
- 2:22:29height as the user scrolls. So, from one
- 2:22:32to zero as the user scrolls up the
- 2:22:34screen. So I'll say
- 2:22:36gap.2
- 2:22:38timeline and I'll set the scale y to 1
- 2:22:44minus
- 2:22:45self.progress. Now if you reload you
- 2:22:48will start seeing the timeline as we hit
- 2:22:50the center of the screen and then it'll
- 2:22:53scroll down and follow us as we go.
- 2:22:56Check this out. And then as you reach
- 2:22:58the end, we cannot go any further right
- 2:23:00now because we don't have additional
- 2:23:01sections. Later on we will. But if you
- 2:23:04start scrolling back up, you can see
- 2:23:06that it starts disappearing. This is a
- 2:23:08super cool effect, isn't it? And the
- 2:23:11last part, we could also animate the
- 2:23:13text on the right. We can make it look
- 2:23:15something like this. Considering that
- 2:23:17the text on the left scrolls from the
- 2:23:19left, we don't have to move this from
- 2:23:22left to right because then a lot of
- 2:23:24things would be moving. We can just fade
- 2:23:26this one in. So, see as we reach this
- 2:23:28point, it'll slowly fade in alongside
- 2:23:32this icon. One more time, check it
- 2:23:35out. It faded
- 2:23:38in. So, let's add that fade in
- 2:23:40animation. To get started with it, I'll
- 2:23:43actually copy this entire gub utils to
- 2:23:47array and then I'll paste it at the
- 2:23:50bottom just before the use gub hook
- 2:23:53stops. In this case, we're not working
- 2:23:55with the timeline card, but rather the
- 2:23:58experience. So, exp text. And for each
- 2:24:01piece of text, we want to run the gap
- 2:24:05from we want to move it from zero
- 2:24:08opacity, 0% because it's not moving.
- 2:24:12Throughout the duration of 1 second,
- 2:24:14we're not moving the origin, so we can
- 2:24:16leave it as it is. Ease can be as it is.
- 2:24:20Scroll trigger will be text and start
- 2:24:22can be top 60. So, we show it a bit
- 2:24:24later than we actually show the left
- 2:24:28side card. Now, if I reload and I scroll
- 2:24:31down, notice how it nicely appears
- 2:24:34alongside this icon. Perfect. Oh, but I
- 2:24:37was looking at the finished website. So,
- 2:24:39let's go to the current one. Reload. And
- 2:24:42same behavior. It's only that we cannot
- 2:24:45see the bottom one yet because we can't
- 2:24:47scroll any further yet. But as soon as
- 2:24:49we add the next section on our list,
- 2:24:51we'll be able to see it. And how does it
- 2:24:53look like on mobile? Well, check this
- 2:24:55out. We still get this timeline where it
- 2:24:58nicely appears and then it starts
- 2:25:00scrolling. We see the hostinger. We see
- 2:25:03the testimonial. We continue scrolling
- 2:25:05down and it looks wonderful here. We can
- 2:25:10even see the Docker one because it has
- 2:25:12more height. Perfect. Alongside the hero
- 2:25:15section, I think this is the second most
- 2:25:17interesting section in this portfolio
- 2:25:19and the second toughest to build. So,
- 2:25:22great job on coming this far into this
- 2:25:24portfolio. The next section is also
- 2:25:26super cool. And here we'll develop these
- 2:25:29cards that have 3D tech icons that
- 2:25:33allows you to move them in space and
- 2:25:35time. Pretty cool, right? So, let's
- 2:25:38focus on the tech stack that you know
- 2:25:40and you use to build your websites with.
- 2:25:42Next, I'll collapse it so we can nicely
- 2:25:45get started on it. Close all of the
- 2:25:47currently open files besides the app.
- 2:25:49and let's focus on the tech
- 2:25:53stack. Let's talk about the text stack.
- 2:25:57In this case, we're talking about what
- 2:25:59you can bring to the table and we'll
- 2:26:01show it by using these animated 3D logos
- 2:26:05for each tool that you're proficient in.
- 2:26:07And of course, if we cannot find a 3D
- 2:26:10icon, I'll provide you with the
- 2:26:11alternative. So, you can just put an
- 2:26:12image right here and it'll still look
- 2:26:14great. So let's create it by creating a
- 2:26:18new section within the sections folder
- 2:26:21and let's call it tech
- 2:26:24stack.jsx. Run rafce and import it
- 2:26:28straight within the app below the
- 2:26:30experience section. Perfect. That's
- 2:26:33looking great. So if we head back over
- 2:26:35here, we can see the text stack and we
- 2:26:37can start by creating the layout. We'll
- 2:26:40wrap everything with a div that has an
- 2:26:42id of skills so we can easily scroll to
- 2:26:45it. Then we can give it a class name of
- 2:26:48flex dash center and
- 2:26:52section-padding. And right within it
- 2:26:54we'll render another div within which we
- 2:26:57will reuse our title header component
- 2:27:00for the consistency of different
- 2:27:03sections. This title header requires two
- 2:27:06different params or props. The first one
- 2:27:09is title. So I'll say my preferred text
- 2:27:13stack. And then below we'll pass a sub.
- 2:27:16I'll enter some kind of an emoji right
- 2:27:18here. Maybe like a deal hands emoji. And
- 2:27:21say what I bring to the
- 2:27:26table. Or we can say the
- 2:27:29skills skills I bring to the table. And
- 2:27:33there we go. We can immediately see it
- 2:27:35right here. So now we can render the
- 2:27:38skills grid with these 3D models. We can
- 2:27:41do that right below the title header.
- 2:27:44But first let's give this div a class
- 2:27:46name of wful full age- full on medium
- 2:27:52devices a padding x of 10 and typically
- 2:27:55a padding x of five. And now right below
- 2:27:58the title header, we can create another
- 2:28:00div with a class name equal to tech d-
- 2:28:04grid. Within this div, we'll want to
- 2:28:07loop through the tech stack icons array
- 2:28:09and create a component for each item. So
- 2:28:12let's open up a dynamic block of code
- 2:28:14and say tech stack
- 2:28:16icons map where we get each individual
- 2:28:20icon. And for each one, we want to
- 2:28:23automatically return a div that'll have
- 2:28:26a key equal to
- 2:28:28icon.name. And within it, we can also
- 2:28:30for now just display that icon name. So
- 2:28:33if you save it, you should be able to
- 2:28:35see React, Python, backend, interactive,
- 2:28:38and project management. Perfect. Now to
- 2:28:41make it look a bit better, we can style
- 2:28:43this div, which will basically be a
- 2:28:45card. So we can give it a card border.
- 2:28:48We can give it a tech card class name as
- 2:28:51well. Overflow of
- 2:28:54hidden group. And on extra-large
- 2:28:57devices, we can make it a rounded full
- 2:29:00and typically rounded LG. Now, what
- 2:29:04matters the most is what we show within
- 2:29:06it. Instead of rendering the icon name,
- 2:29:09I'll first render a self-closing div
- 2:29:12that'll have a class name equal to tech
- 2:29:15card animated bg. What this will do is
- 2:29:20once you hover over it, it'll actually
- 2:29:22animate it a bit, which is not
- 2:29:23mandatory, but I think it has a cool
- 2:29:25effect. After that, we can render
- 2:29:27another div that'll contain the content.
- 2:29:31So, let's give it a class name of tech
- 2:29:33card content.
- 2:29:36And within it we can create a wrapper
- 2:29:38that contains the tech card experience
- 2:29:42which will render the 3D model of that
- 2:29:44specific tech stack icon. So let me
- 2:29:47create a div with a class name equal to
- 2:29:52tech
- 2:29:53icon wrapper and directly within it we
- 2:29:57can render a new self-closing component.
- 2:29:59So let's create it under the components
- 2:30:02folder. And we can create a new folder
- 2:30:05and call it models as in 3D models. And
- 2:30:09then within it we can create another
- 2:30:11folder which we can call tech
- 2:30:14logos. And within it we can create a new
- 2:30:18tech
- 2:30:20icon.jsx and
- 2:30:23runce. Perfect. So now we have this tech
- 2:30:26icon and we can import it right within
- 2:30:28the text stack where we have the tech
- 2:30:30icon wrapper. I'll call it tech icon
- 2:30:34like this and pass in the model right
- 2:30:37into it by getting it directly from the
- 2:30:40icon. So, we're mapping over these text
- 2:30:43stack icons. And each one of these icons
- 2:30:46has a name, a path to the model that is
- 2:30:49within our public models folder, a
- 2:30:52scale, and a rotation in case you want
- 2:30:55to change it by default. And then we're
- 2:30:56passing it over here. So now, if you
- 2:30:59compare it, you'll see that we have
- 2:31:01different tech icons and they're not yet
- 2:31:04in 3D, but they soon will be. Right
- 2:31:06below this div that's a wrapper for the
- 2:31:08tech icon, we can have another div
- 2:31:11that'll have a class name of padding
- 2:31:15x and w full. And we use these classes
- 2:31:19to add horizontal padding to the text
- 2:31:22and to make it take up the full width of
- 2:31:24the
- 2:31:25component. So within it, we can display
- 2:31:28the name of that icon. So I'll say
- 2:31:32icon.name. Perfect. So now we have React
- 2:31:35developer right here and then we have
- 2:31:37the icon which wouldn't display in the
- 2:31:39middle. And let's just make sure to wrap
- 2:31:41it in a P
- 2:31:43tag. So if we close it like this, you
- 2:31:46can see that it'll nicely get centered.
- 2:31:48Now we can head over into the tech icon
- 2:31:51and start implementing that rotating 3D
- 2:31:54model for each one of the tech logos
- 2:31:56inside of a canvas. Remember whenever
- 2:31:58you have a 3D experience, you can show
- 2:32:01it within a canvas. So before we get
- 2:32:03started, let me explain how it works. In
- 2:32:06simple terms, this is exactly what we
- 2:32:09want to do. First, we want to load a GB
- 2:32:12model in a gltf format. Then we want to
- 2:32:16add basic lighting to it so we can see
- 2:32:18it. We want to apply environment
- 2:32:20reflections for realism. Wrap the model
- 2:32:23in a floating animation. And then
- 2:32:25optionally tweak the material like
- 2:32:27setting the color on a mesh. And then we
- 2:32:29want to disable zoom because there's
- 2:32:31nothing to zoom into here. We just want
- 2:32:33to be able to move it around. So let's
- 2:32:35do just that. We'll start by loading in
- 2:32:39the models right here. I'll say const
- 2:32:42scene is equal to use gltf coming from
- 2:32:47react 3. And I'll just get in the model
- 2:32:51from the props that we're passing into
- 2:32:53the function. So I'll say
- 2:32:55model. Keep in mind we're passing it
- 2:32:57right here. model is the icon. And then
- 2:33:00I'll simply load in
- 2:33:03model
- 2:33:05path. Perfect. We just loaded the GB
- 2:33:08model using Dre's use GLT of hook. And
- 2:33:11the file path comes from the model model
- 2:33:14path which is right here within our
- 2:33:17constants file. So if you want to
- 2:33:19compare it, check out the public folder
- 2:33:22and then head over to models and then
- 2:33:25you'll be able to see Python, 3JS,
- 2:33:28React, Node, and Git. Perfect. Now that
- 2:33:32we have loaded in the model, we want to
- 2:33:34set up the canvas. So think of the
- 2:33:35canvas as the starting div in the 3D
- 2:33:38world. You cannot display 3GS properties
- 2:33:42within a div. If you tried putting some
- 2:33:44ambient lighting here like this, it
- 2:33:48wouldn't work. But if you render a
- 2:33:50canvas right here coming from React 3
- 2:33:53fiber, then you can start using all of
- 2:33:55these properties. So let's add a basic
- 2:33:58fill light with an
- 2:34:01intensity of about
- 2:34:030.3. Once again, we can play with that
- 2:34:05very soon. And right below it, I'll add
- 2:34:08an
- 2:34:10environment coming from React 3J with a
- 2:34:14preset set to city. So there are
- 2:34:17actually a couple of presets that you
- 2:34:18can choose from to load your elements
- 2:34:21within. This city environment will add
- 2:34:23specific ambient lightning to achieve a
- 2:34:26realistic scene reflection. It makes
- 2:34:28metal and glossy surfaces look real with
- 2:34:31minimal setup. Then we're going to wrap
- 2:34:34everything in a floating animation. So
- 2:34:36I'll say float coming from React
- 2:34:393J and within it I'll wrap a group and
- 2:34:44within that group I will render a
- 2:34:47primitive specifically an object of
- 2:34:51scene scene. There we go. So we can
- 2:34:54already start seeing some logos right
- 2:34:56here. But you can see that they're not
- 2:34:57looking that good. It's bad actually. So
- 2:35:01let's modify the float. I'll give it a
- 2:35:04speed equal to
- 2:35:065.5. And now you can see they're moving
- 2:35:09around kind of. I'll also set the
- 2:35:12rotation intensity to 0.5. So they're
- 2:35:15moving a bit slower. And I'll also give
- 2:35:17it a float intensity of around
- 2:35:210.9. So now they're floating even more
- 2:35:24subtly. But again, you can totally
- 2:35:26change this. You can increase the
- 2:35:28rotation intensity to one or maybe even
- 2:35:30two if you really want them to move
- 2:35:33around or keep it a bit more subtle.
- 2:35:35Next, we can also play with the group by
- 2:35:38giving it a scale equal to you can make
- 2:35:41it two, you can make it three. Of
- 2:35:43course, it's going to be too big. So, I
- 2:35:46decided to provide a scale already
- 2:35:49within the model. So, you can just say
- 2:35:51model.scale, scale which is going to
- 2:35:52scale all of these accordingly because
- 2:35:54some are bigger and some are smaller by
- 2:35:57default. So this is coming directly from
- 2:36:00the index of the constants where you can
- 2:36:03see that scale is one for react because
- 2:36:05it's just the right size but for example
- 2:36:08python was too large so I lowered it
- 2:36:10down a bit and the node.js was too small
- 2:36:13so I had to increase the scale
- 2:36:15significantly. We can also do the same
- 2:36:17thing with rotation. So I'll say
- 2:36:19rotation is equal to model. rotation.
- 2:36:23Perfect. So now they're all facing right
- 2:36:26toward the user. As you can see, the
- 2:36:28float component is pretty cool. It's a
- 2:36:30utility from React 3J library used to
- 2:36:33create a simple animation of the model
- 2:36:35floating in space. And what do you say
- 2:36:37that we also apply some additional light
- 2:36:40to make it look a bit more intentional?
- 2:36:42Let's say below the ambient light, which
- 2:36:46you can see we can also play with it. If
- 2:36:48I lower the intensity to zero, well, you
- 2:36:51can't really see the difference. But if
- 2:36:52I do it to one, they're going to shine
- 2:36:54really bright, which we don't really
- 2:36:56need. So, I'll keep it very
- 2:36:58low. But next, we can add a
- 2:37:01directional
- 2:37:03light. Position
- 2:37:05it around
- 2:37:08555. Of course, we have to spell it
- 2:37:10correctly so our app doesn't break.
- 2:37:12Directional. And I'll set the
- 2:37:15intensity to one. Again, makes a little
- 2:37:18difference. You cannot really see it a
- 2:37:20lot with the React logo, but if you pay
- 2:37:22attention to the NO.js logo, you'll see
- 2:37:25how the corners shine just a bit
- 2:37:27brighter. And you'll see it even more
- 2:37:29once we allow you to move the model
- 2:37:32around or orbit it around. For that,
- 2:37:36we'll use the orbit
- 2:37:37controls. We have already used it
- 2:37:39before. So you can just say orbit
- 2:37:42controls and just pass the enable zoom
- 2:37:46set to false because we don't want to
- 2:37:47zoom in. We just want to be able to move
- 2:37:49them around. And check this out. Just
- 2:37:51like that, you can move this logo around
- 2:37:54and see it in all different lights.
- 2:37:57Great. Now, there's just one tweak I
- 2:37:59want to do, and that is to change the
- 2:38:01color of this 3JS logo. Originally, it
- 2:38:04is black, but it's not really looking
- 2:38:06good alongside our background. So, what
- 2:38:09do you say that we tweak it just a tiny
- 2:38:11bit? I'll add a use effect, which will
- 2:38:14allow us to apply a specific effect as
- 2:38:16our components load just initially on
- 2:38:19mount. And we want to do it whenever the
- 2:38:21scene changes. So, if we load the scene
- 2:38:24later on, then we want to apply to a
- 2:38:26specific scene. I'll then say if
- 2:38:29model.name name. Oh, and don't forget to
- 2:38:32import use effect coming from React.
- 2:38:35That's very important. So now if model
- 2:38:39name is triple equal to
- 2:38:43interactive
- 2:38:45developer in that case we want to set
- 2:38:49the scene
- 2:38:52scene.traverse. So we want to traverse a
- 2:38:55child element of that scene and we want
- 2:38:58to check if child is
- 2:39:01mesh and if
- 2:39:04childname is triple equal to
- 2:39:08object_5. It took me some time to find
- 2:39:10it but basically we're targeting that
- 2:39:12specific model of 3JS. Then I want to
- 2:39:16set the child material to be equal to
- 2:39:19new three dot mesh
- 2:39:24standard
- 2:39:26material and I want to pass in a color
- 2:39:29of white and to be able to use this
- 2:39:32three make sure to import everything as
- 2:39:37three from three. Perfect. So if you do
- 2:39:42this, you can see that we can now change
- 2:39:43the color or we can specifically target
- 2:39:46the material of this specific mesh. You
- 2:39:50can change it to red, but you have to
- 2:39:52reload the screen. There we go. But of
- 2:39:54course, we're going to go with white. It
- 2:39:56looks very good on our background. There
- 2:39:58we go. This is nice. Now, it's possible
- 2:40:01that maybe you don't know Python and
- 2:40:05you're wondering how you can replace
- 2:40:06this for some other technology that you
- 2:40:08want to include here. Don't worry. I'm
- 2:40:10going to teach you how to do just that
- 2:40:12very soon. But before that, there's just
- 2:40:14one little part missing from this
- 2:40:17section, and that are of course
- 2:40:19animations. So, let's go back to the
- 2:40:22text stack component, and let's animate
- 2:40:25these icons one by one. I'll of course
- 2:40:29use the use Gap hook coming from Gap
- 2:40:33React. We will target it like this. And
- 2:40:36within it I'll say GSAP which we have to
- 2:40:39import of course. So import GAP from
- 2:40:43GSAP. Oh, I think it has to be within
- 2:40:46curly braces actually. So I'll say GSAP
- 2:40:49from
- 2:40:50two. What do I want to animate? I want
- 2:40:53to animate the tech
- 2:40:55card. At the start I want to move the
- 2:40:58card down by 50 pixels and set the
- 2:41:03opacity to zero.
- 2:41:06And then at the end I wanted to move the
- 2:41:10card back to the top. I want to reset
- 2:41:13the opacity to one throughout the
- 2:41:15duration of 1 second. Ease can be set to
- 2:41:19power two.in out which we used before as
- 2:41:22well. Stagger can be 0.2 which means
- 2:41:26that each animation will happen 0.2
- 2:41:29seconds after the other. And then we can
- 2:41:31also add a scroll
- 2:41:33trigger. and we want to trigger it once
- 2:41:36we get to the skills section and we want
- 2:41:39to start it once we reach the top center
- 2:41:42part of the screen. So now if you reload
- 2:41:46and start scrolling down you can see how
- 2:41:48they will all load one after another.
- 2:41:51But of course that looks even better on
- 2:41:53desktop. So if you reload and go to the
- 2:41:56bottom, check this out. 0.2 seconds for
- 2:41:58each one. Let me show you one more
- 2:42:01time. There we go. So they all load from
- 2:42:04the bottom to the top and they also go
- 2:42:06from opacity zero to opacity 1. And you
- 2:42:10can see them all like
- 2:42:12this. This is looking great. You can
- 2:42:16also spin them around super fast. That's
- 2:42:19cool. And um I guess the question is
- 2:42:22what if you don't know Python? Well, I
- 2:42:24prepared an additional part of this
- 2:42:26section for you. So you can replace this
- 2:42:28with any other static image. or no, it
- 2:42:31doesn't have to be static. If you want
- 2:42:33to take some time and search the web for
- 2:42:36some other 3D icons of technologies that
- 2:42:38you know, go ahead. Maybe go to
- 2:42:40SketchFab and search for something like,
- 2:42:43I don't know, Nex.js, but it might be
- 2:42:46hard to find the wider version of the
- 2:42:47NextG.js logo. Or maybe you want to add
- 2:42:49something else. If you can find a 3D
- 2:42:51model for it, just plug it in, point to
- 2:42:55its path. So, head over to the constants
- 2:42:58index.js JS and just add it to the
- 2:43:01models, point to that 3D file, most
- 2:43:03likely with a GB extension, and load it
- 2:43:06in. But maybe you won't be able to find
- 2:43:083D icons for all of these different
- 2:43:12technologies. So, let me show you how we
- 2:43:14can make it work with the regular
- 2:43:16images. I'll head back to the text
- 2:43:18stack, and I want to go after we finish
- 2:43:20mapping over these icons, and I'll add a
- 2:43:22new part. I'll once again map over the
- 2:43:25text stack images this time because I
- 2:43:27also prepared some images for you. I'll
- 2:43:30say map. We get an individual icon. And
- 2:43:34for each icon, we want to display a div.
- 2:43:37This div will have a class name equal to
- 2:43:41very similar to what we had above. So
- 2:43:43let me actually copy it. This div right
- 2:43:46here. And I will paste it here. Within
- 2:43:49it, we can display a self-closing div
- 2:43:52with a class name equal to tech card
- 2:43:57animated bg as before. And we can open
- 2:44:02up a div that'll act as the content
- 2:44:05wrapper. So I'll say tech card
- 2:44:09content. And then within it, a div
- 2:44:12that'll have a class name of tech icon
- 2:44:15wrapper. Still the same as above, but
- 2:44:18the only difference is that here now we
- 2:44:20can display a regular image. So source
- 2:44:24of icon dot image path instead of a
- 2:44:28model. And then right below it we can
- 2:44:30render another div that'll have a class
- 2:44:33name of padding x and wful and it'll
- 2:44:38render a p tag that'll render the
- 2:44:41icon.
- 2:44:43So if you save this, you'll see that
- 2:44:45just below we have an image variation of
- 2:44:48the same thing. Now is this one better?
- 2:44:51Well, of course it is. And for some you
- 2:44:54can have 3D models, but for others, if
- 2:44:56you cannot find the logo in the 3D
- 2:44:59format, then you can use this section
- 2:45:02right here because this is much easier
- 2:45:04to replace. You can go to constants text
- 2:45:07stack images and just download the PNG
- 2:45:10image, put it in the path and then
- 2:45:12change the name map over it and you're
- 2:45:15good. I got you covered. I know that you
- 2:45:17guys know so many technologies and I
- 2:45:19wanted to make this portfolio as
- 2:45:21customizable as possible for you. So,
- 2:45:25for now, I'm going to use my text stack
- 2:45:283D models, but you can opt for using
- 2:45:31just regular images in case you want to
- 2:45:33have a consistent showcase of the text
- 2:45:35stack that you specifically know. And
- 2:45:38that brings us to the end of the text
- 2:45:40stack section, which looks great on
- 2:45:42mobile as well as it does on desktop.
- 2:45:45So, in the next section, let's focus on
- 2:45:48the testimonial section, allowing you to
- 2:45:51brag yourself a bit, not by saying good
- 2:45:53things about yourself, but by showcasing
- 2:45:56what other people have to say about
- 2:45:59you. The testimonial section is pretty
- 2:46:03simple, yet as you start hovering over
- 2:46:05it, you'll see that it is pretty cool. I
- 2:46:07mean, the goal of your websites is not
- 2:46:10to be flashy. Things don't need to jump
- 2:46:13around. They have to be minimal, but
- 2:46:15still they have to showcase what you're
- 2:46:16capable of, which is exactly what we do
- 2:46:19with this simple testimonial section. A
- 2:46:21cool thing about it is, of course, this
- 2:46:24border that we have implemented. And an
- 2:46:26even cooler thing is just how quickly
- 2:46:29we'll be able to get it implemented now
- 2:46:31that we already have this reusable card.
- 2:46:34Let me show you. I'll create a new
- 2:46:36section within the sections folder, and
- 2:46:39I'll call it testimonials.jsx.
- 2:46:42JSX. I'll run rafce and import it within
- 2:46:47our
- 2:46:48app.jsx right below the text stack. I'll
- 2:46:50head over into it and start implementing
- 2:46:53the layout. First, I'll wrap everything
- 2:46:56in a semantic section tag with an ID of
- 2:46:59testimonials so we can scroll to it from
- 2:47:02the navbar. And I'll give it a class
- 2:47:04name equal to flex- center and section
- 2:47:09dash padding so it has some nice space
- 2:47:11to
- 2:47:12breathe. Next, I'll render a div that'll
- 2:47:15wrap our title. So, let me give it a
- 2:47:18class name equal to w full hful on
- 2:47:22medium devices a padding x of 10 and
- 2:47:25typically a padding x of 5. And within
- 2:47:28it, I'll render a reusable title header
- 2:47:32component with a title of what people
- 2:47:37say about me. And then I'll add a
- 2:47:42subtitle. Maybe we render an emoji of a
- 2:47:46star. And then say
- 2:47:49client feedback highlights. And if you
- 2:47:53scroll down, looks good and is
- 2:47:55consistent with other sections we have
- 2:47:57on the screen. But now is the time that
- 2:47:59we render the grid of these cards. We'll
- 2:48:02use a multicolumn layout similar to this
- 2:48:06masonry style card layout that you often
- 2:48:09see on these websites like Pinterest
- 2:48:11where you have images coming in at
- 2:48:13different heights within different
- 2:48:15columns. It creates this visually
- 2:48:17interesting layout that says, "Hey, look
- 2:48:20at how much content there is. It is
- 2:48:23basically infinite because you can only
- 2:48:25see half of this card here, which means
- 2:48:27there's more at the top, and you can
- 2:48:29only see half of this card here, which
- 2:48:31means that there's more at the bottom.
- 2:48:33And you want to have a similar feel with
- 2:48:36your testimonial grid as well to let
- 2:48:38people know that there's just so much
- 2:48:40good things to say about you. So below
- 2:48:42the title, I'll render a div that'll
- 2:48:45have a class name equal to on large
- 2:48:49devices, give me three columns, on
- 2:48:52medium devices, give me two columns,
- 2:48:55otherwise give me one column and give me
- 2:48:59some margin top of about 16. within it
- 2:49:04we can say
- 2:49:05testimonials dom where we get each
- 2:49:09individual
- 2:49:10testimonial and for each one for now we
- 2:49:13can render well let's do testimonial
- 2:49:17review so I'll say P tag and render
- 2:49:23testimonial
- 2:49:24review of course make sure to import
- 2:49:27testimonials from the constants index
- 2:49:31and give each one a name a mention maybe
- 2:49:34to their socials, their actual review,
- 2:49:36and a path to the image. Of course,
- 2:49:39you'll have to take some time to go
- 2:49:40through these and actually change them
- 2:49:42to the feedback that you were given.
- 2:49:45Hopefully, working with you was even
- 2:49:47better than working with me. So, if you
- 2:49:49do that, you'll be able to see this
- 2:49:51scattered feedback right here. But now,
- 2:49:53we want to display it in a very nice
- 2:49:56layout. And doing that will be super
- 2:49:58easy. We just have to reuse the glow
- 2:50:02card. Remember that card that we used
- 2:50:04right before here? We'll just reuse it.
- 2:50:08Let me show you
- 2:50:09how. I will wrap everything in a glow
- 2:50:14card. And I'll pass in some children,
- 2:50:17specifically a div that has a class name
- 2:50:21of flex items center and a gap of three.
- 2:50:25And here I can say test for now.
- 2:50:29If you do that, the website will break
- 2:50:31because you also have to pass a prop to
- 2:50:33the glow card of card is equal to
- 2:50:37testimonial so that it knows what to
- 2:50:39render. Now, if you scroll down, you'll
- 2:50:41be able to see five different cards that
- 2:50:45all say test at the bottom. But already
- 2:50:48half of it is done. We have five stars
- 2:50:50and the reviews from the testimonial are
- 2:50:54getting passed over to the card. The
- 2:50:56only remaining part is to show who gave
- 2:50:58us that
- 2:50:59feedback. So I'll create another div
- 2:51:02within this inner div that'll contain an
- 2:51:05image with a source of
- 2:51:09testimonial img
- 2:51:12path with an all tag equal to
- 2:51:16testimonial name. There we go. So we can
- 2:51:18see different people. Right now, we can
- 2:51:21create another div right below it
- 2:51:24that'll render a p tag with a class name
- 2:51:28equal to
- 2:51:29font-bold. And this one can say
- 2:51:31testimonial name. I now see that we're
- 2:51:34repeating ourselves too much.
- 2:51:35Testimonial, image path, name, and so
- 2:51:38on. So, let me just dstructure those
- 2:51:40values. Image
- 2:51:42path, name, and mentions. And now you
- 2:51:47don't have to repeat yourself here. You
- 2:51:49can just say image path. Here you can
- 2:51:51say name. Here where you're rendering a
- 2:51:53P tag, you can also render a name. But
- 2:51:56now you'll have to pass all of these
- 2:51:58within an object to the card because we
- 2:52:00no longer have access to the original
- 2:52:02testimonial. So we have to pass all of
- 2:52:04them. Name, mentions, review, and image
- 2:52:07path. But I think the glow card itself
- 2:52:10only uses a couple of things. It uses
- 2:52:12the review. Yeah, that's it. So, we just
- 2:52:15need to pass over a review right here,
- 2:52:18which we get over from the
- 2:52:20card. If I save this and scroll to the
- 2:52:24bottom, you can see that we still have
- 2:52:26five stars, the review, and now we also
- 2:52:29have the photo and the name of the guy.
- 2:52:32But to increase the credibility even
- 2:52:34further, we can render another P tag
- 2:52:36here with a class name of text white 50.
- 2:52:41And we can render the mention of that
- 2:52:44guy or gal uh which gives us their
- 2:52:47socials. So if somebody wants to verify
- 2:52:49whether this guy is a real guy uh well
- 2:52:52then they would have to maybe check them
- 2:52:54out on Twitter or something like that.
- 2:52:56Most developers nowadays have active
- 2:52:58Twitter profiles. Also I noticed that
- 2:53:00we're missing some space in between the
- 2:53:02cards vertically on mobile devices. So
- 2:53:05if you head over into the glow card, we
- 2:53:08can add some additional classes to it
- 2:53:10such as a margin bottom of five and also
- 2:53:14break inside a void column. This is a
- 2:53:18special CSS property that sets how page
- 2:53:21column or region breaks should behave
- 2:53:23inside a generated box. It is perfect
- 2:53:26just for this use case where you have
- 2:53:27different cards one on top of another.
- 2:53:30So this is great. And with that, we're
- 2:53:32actually done with the testimonial
- 2:53:34section. So far, we have only seen it on
- 2:53:37mobile, but now check it out on desktop.
- 2:53:41We have this nice layout where this card
- 2:53:43appears to be taking some more space
- 2:53:45than this one, and so on. And the more
- 2:53:48cards you add with different heights of
- 2:53:50the content, the better it'll look like.
- 2:53:53Remember that masonry Pinterest grid? If
- 2:53:55you head over into the testimonials and
- 2:53:58maybe duplicate a couple of these right
- 2:54:01here and save it, you can see how it
- 2:54:04adapts to the amount of content we have.
- 2:54:07So, go ahead and collect those positive
- 2:54:10testimonials. It's always great to have
- 2:54:12some experts vouch for you. With that
- 2:54:15said, we're getting very, very close to
- 2:54:17the bottom of our page. Nonetheless,
- 2:54:20it's going to be an amazing finishing
- 2:54:22touch where we'll implement this fully
- 2:54:25functional form that'll allow other
- 2:54:27people to contact you and then we'll
- 2:54:29implement another super cool artistic 3D
- 2:54:32model that basically says, "Hey, I'm
- 2:54:35going to be at my desk. Feel free to
- 2:54:36send me an email." So, let's do that.
- 2:54:41Next, last but not least section of the
- 2:54:44portfolio and maybe the most important
- 2:54:46one because without it, how would people
- 2:54:48be able to reach you to hire you? So,
- 2:54:51let's get started with implementing the
- 2:54:53contact section. Back in our project,
- 2:54:55the last section we implemented was the
- 2:54:58testimonial section. So, now we have to
- 2:55:00add the contact section just below. And
- 2:55:03recently I saw that Jet Brains released
- 2:55:05a new coding agent that can speed up our
- 2:55:08workflow. So I wanted to test it a bit
- 2:55:10when creating this contact section. I'll
- 2:55:12press command shiftp and open up Juny.
- 2:55:15That's the name of our assistant. If
- 2:55:16you're using WebStorm and you want to
- 2:55:18test it out with me, I'll leave the link
- 2:55:20down in the description. But for now,
- 2:55:21let's try to be precise. Let's say
- 2:55:24create a new section called contact.
- 2:55:28imported within the app and within it
- 2:55:32use the class names that we typically
- 2:55:37wrap all of our sections with and also
- 2:55:42add a title header
- 2:55:44component right within it that
- 2:55:47says something about contacting us. I'll
- 2:55:51turn on the brave mode and give it a go.
- 2:55:54It is working right now and it actually
- 2:55:56outlined a plan. Examine the current
- 2:55:58structure. Look at the title header
- 2:56:00component since we need to use it.
- 2:56:02Create a new context file in the
- 2:56:04sections directory. Implement the
- 2:56:06contact component with appropriate class
- 2:56:07names in the title header. And then
- 2:56:09import and add the contact component to
- 2:56:11app.js. So right now it's actually doing
- 2:56:14all of that. It's opening all of these
- 2:56:16things, editing different pages. It
- 2:56:18looks like it actually created a new
- 2:56:20contact section right here within
- 2:56:23sections contact and it ended up using
- 2:56:27the title header right here alongside
- 2:56:28with some other information. Pretty
- 2:56:30cool. Now it's editing the app and
- 2:56:32adding the contact section right within
- 2:56:34it by following the same structure we
- 2:56:36used before. And it told us exactly what
- 2:56:39it did right here. So we can roll back
- 2:56:41or just call it a day. Now, if I save
- 2:56:44this and give Juny some time to rest,
- 2:56:47let's see what we have. So, it looks
- 2:56:49like it did exactly what I asked it to
- 2:56:51do. It created a new section right here
- 2:56:54within the sections directory called
- 2:56:56contact. It gave it an ID so we can
- 2:56:58scroll to it alongside some default
- 2:57:00class names. And it also used our title
- 2:57:03header component with a title as well as
- 2:57:05a sub. And it even added an emoji
- 2:57:08because it noticed that that's exactly
- 2:57:09what we're doing for other sections. And
- 2:57:11then since I wasn't precise on what we
- 2:57:13want to see below, it created a generic
- 2:57:16structure where somebody can actually
- 2:57:18copy this email and send it to us.
- 2:57:20Pretty cool stuff that it was able to do
- 2:57:22all of this. Not just generate code, but
- 2:57:24also create files for us. If you didn't
- 2:57:27end up using Juny just yet, that's
- 2:57:29totally fine. You can pause this video
- 2:57:31and copy this code right here where it
- 2:57:34generated this title header. But can we
- 2:57:36take it a step further? Can we maybe get
- 2:57:39it to create a layout right here where
- 2:57:42we're going to have a form and then also
- 2:57:46it'll create a new component on the
- 2:57:48right side that'll allow us to display
- 2:57:50this greatl looking 3D model. Well,
- 2:57:53let's give it a shot. I'll once again
- 2:57:55open up Juny and say something like
- 2:57:58within the contact
- 2:58:00section just
- 2:58:02below the title
- 2:58:04header. And by the way, I haven't used
- 2:58:07Juni before, so I don't know how precise
- 2:58:09I have to be. So, better to be as
- 2:58:11descriptive as possible, but maybe it
- 2:58:13works with less info as well. I don't
- 2:58:15know. So right here I'll say within the
- 2:58:18context section just below the title
- 2:58:20header create a layout with a grid 12
- 2:58:25calls class where on the left side we'll
- 2:58:29have a form that has labels and inputs
- 2:58:33for for the name, the email and the
- 2:58:38message and of course a submit button.
- 2:58:42You can find some classes within the
- 2:58:45index.css to keep it consistent. And
- 2:58:48then on the
- 2:58:50right side of that form create and
- 2:58:53display a new
- 2:58:56component called contact
- 2:58:59experience within which we'll later
- 2:59:04on display a
- 2:59:063JS canvas by importing a computer
- 2:59:12optimized.gb GB file and
- 2:59:16immediately add some lights and meshes
- 2:59:20so we can see it on the
- 2:59:22screen. This is a super hard task and
- 2:59:25I'm not expecting Juny to get it right
- 2:59:27on the first time, but who knows? Let's
- 2:59:30see. I love testing these AI tools as
- 2:59:32they just make you that much more
- 2:59:34efficient of a developer. There we go.
- 2:59:37It's researching everything just to get
- 2:59:39some context and then it'll try to
- 2:59:41create stuff. Okay, looks like it
- 2:59:43started editing the contact experience
- 2:59:45part. So, if we head over to our
- 2:59:47components, we can see the contact
- 2:59:49experience and it is writing code right
- 2:59:51now already putting in the canvas and
- 2:59:54some lights and meshes and the
- 2:59:55environment. Looks like it's keeping in
- 2:59:57mind what we did for the hero section
- 2:59:59and it's done. Juny says, "A grid layout
- 3:00:02was implemented in the contact section
- 3:00:04featuring a form on the left for name,
- 3:00:06email, and message inputs alongside a
- 3:00:09new contact experience component
- 3:00:10displaying a 3D computer model using 3JS
- 3:00:13on the right. The implementation is
- 3:00:15error-free and all components function
- 3:00:18as intended. This is super cool, but
- 3:00:21let's see if that is really the case. It
- 3:00:24looks like it actually generated a form
- 3:00:26that is super similar to the one on the
- 3:00:28final application. Just a couple of
- 3:00:30tweaks and it'll be there. And it also
- 3:00:33left some space right here on the right
- 3:00:34to display our canvas, but we'll have to
- 3:00:36make a couple of tweaks to actually get
- 3:00:38it to show. So, let's explore what it
- 3:00:40did within the code. If I head over to
- 3:00:43the app, we still have the contact
- 3:00:45section. It created some states handle
- 3:00:48changes as well. Oh, so actually we can
- 3:00:51already type within this form. That's
- 3:00:53super cool. And we have a form where we
- 3:00:55can actually submit that data. And after
- 3:00:57that we have our div for the left side
- 3:01:01of the form. Let's go through it
- 3:01:02together. So if you haven't used Juny,
- 3:01:04we can actually go ahead and write it
- 3:01:06together ourselves manually. Okay, first
- 3:01:08things first. Right below our title
- 3:01:11header, we have a div that has a class
- 3:01:14name of margin top of 16 and grid 12
- 3:01:19columns.
- 3:01:20Then we have a contact form left side
- 3:01:23that has a div where we have on
- 3:01:25extra-large devices call span of six. In
- 3:01:29this case we can maybe make it five so
- 3:01:31that then the right side takes a larger
- 3:01:34portion of the screen. And I'll also
- 3:01:35remove this part. We won't need it as
- 3:01:37later on as we continue developing our
- 3:01:39layout we'll make sure that it all fits
- 3:01:41nicely if we're on mobile devices. So
- 3:01:43within this div, I'll create another div
- 3:01:46that'll have a class name equal to
- 3:01:50flex-c center
- 3:01:52card-border rounded- excel and a padding
- 3:01:55of 10. And within it, I want to bring my
- 3:01:58form. So let me copy the form and just
- 3:02:01bring it a couple of steps up right here
- 3:02:05within this
- 3:02:06div. If you now save this, you'll notice
- 3:02:09that it looks something like this. Make
- 3:02:11sure to give it a class name equal to w-
- 3:02:15full flex flex- call and a gap of seven.
- 3:02:20And let's look into the top part. We
- 3:02:22have an id of contact flex center
- 3:02:25section padding. We have a div that has
- 3:02:28a class name of w full h full on medium
- 3:02:32devices padding x of 10 and typically
- 3:02:34padding x of 5. Then just below the
- 3:02:36title header, we have this div that then
- 3:02:39contains the left side of the form.
- 3:02:41Let's just make sure that the right side
- 3:02:42is not included within that div. So for
- 3:02:45the 3D experience, I'll give it an
- 3:02:47extra-large call span 7 so it takes a
- 3:02:50bit more space. I'll remove the call
- 3:02:52span 12 and I'll set the min h to 96. So
- 3:02:58now you'll see that this will take seven
- 3:03:00out of 12 spaces and this form will take
- 3:03:03five out of 12. But on mobile devices,
- 3:03:06it'll actually take the full width of
- 3:03:07the screen. I can actually explain that
- 3:03:10by providing a border two and border
- 3:03:13solid red. And you can see that this
- 3:03:15takes this part of the screen right now.
- 3:03:17But if I do the same thing for the
- 3:03:20bottom one, so for the right side, it'll
- 3:03:24look something like this where now it
- 3:03:26takes the bottom part of the screen. But
- 3:03:28as soon as you expand it, you can see
- 3:03:29that this takes five out of 12 and this
- 3:03:32one takes 7 out of 12. which means that
- 3:03:34our layout is great. So, I'll remove
- 3:03:36those borders and we can continue with
- 3:03:38the rest of the code. I know that it's
- 3:03:40possible that you might not have had
- 3:03:42this code at the bottom. So, if this is
- 3:03:44getting a bit more complicated, I'll
- 3:03:46also provide a complete contact.jsx file
- 3:03:49within the video kit. So, you can copy
- 3:03:51it and have this form right here. But
- 3:03:53with that said, don't worry because we
- 3:03:55can also type it out together. Next, we
- 3:03:57have the form that has a class name of W
- 3:03:59full flex flex colon a gap of seven. And
- 3:04:02within it, we have a div. I don't think
- 3:04:05this div will need a class name for a
- 3:04:07margin bottom. We can just use the gaps
- 3:04:09to provide that spacing. We have a label
- 3:04:12for HTML 4 for the name. We have an
- 3:04:16input that has a type of text, an ID of
- 3:04:19name, a placeholder of your name, and
- 3:04:22some parameters that we need to manage
- 3:04:24its value. After that, we have another
- 3:04:27div that has a label for email and also
- 3:04:30some properties for the email. And
- 3:04:32finally, we have another one right at
- 3:04:35the bottom that's managing the message
- 3:04:37property which is actually a text area.
- 3:04:40Finally, after that div containing the
- 3:04:42text area, we have the button with a
- 3:04:45type submit. And in this case, we can
- 3:04:47style it a bit differently by rendering
- 3:04:50a div right within the button that'll
- 3:04:53have a class name equal to CTA button
- 3:04:58group. within it, we will display a div
- 3:05:02that'll have a class name of BG
- 3:05:06circle. And you can notice that I'm
- 3:05:08recreating the button we had at the top.
- 3:05:10That's because this one will be an
- 3:05:11actual submit button, whereas the
- 3:05:14previous button was not really a button.
- 3:05:17Technically, it was an anchor tag that
- 3:05:19we used to scroll to some places. Right
- 3:05:21below this div that says bg circle, I'll
- 3:05:24render a p tag. That'll render a text of
- 3:05:27send
- 3:05:28message and it'll have a class name
- 3:05:31equal to text. And below it, I'll render
- 3:05:34a div with a class name equal to arrow-
- 3:05:38wrapper and it'll render a self-closing
- 3:05:41image with a source of forward slash
- 3:05:45images/ arrow-down.
- 3:05:48SVG and an all tag of arrow. If you save
- 3:05:52that, we'll once again have this
- 3:05:55beautiful button that allows you to send
- 3:05:57the message. Then if we go a bit down
- 3:06:00below the div, below the button, and
- 3:06:03below the form and below two more divs,
- 3:06:06we get to the second part of the screen.
- 3:06:09Just a simple div with a min height
- 3:06:11taking seven out of 12 spaces on extra
- 3:06:14large devices. And within it, we can
- 3:06:16display another div within which we'll
- 3:06:18put this contact experience, which you
- 3:06:21might not have yet if you haven't used
- 3:06:23Juny, but don't worry because we're
- 3:06:24going to create it together. This div
- 3:06:26will have a class name equal to W full
- 3:06:31for full width, H full for height, and a
- 3:06:34background color. Let's make this a
- 3:06:36bright orange color that you saw in the
- 3:06:38preview of CD7 C2E.
- 3:06:43If you save it, oh, it looks like we
- 3:06:45already get some kind of a canvas right
- 3:06:47here, or at least a background color.
- 3:06:49Let's make it a hover cursor grab. So,
- 3:06:53we give a user an indication that they
- 3:06:55can actually move around this 3D
- 3:06:57experience. Let's round up the corners a
- 3:07:00bit by saying rounded 3XL. And let's the
- 3:07:03overflow to hidden. If you save this,
- 3:07:07this is looking great, but it looks like
- 3:07:08nothing's on the screen. What if I
- 3:07:10expand it? Oh, still nothing. But if I
- 3:07:14drag and drop, oh, would you look at
- 3:07:16that? The 3D model is actually here, but
- 3:07:18we are at the bottom of the table. So,
- 3:07:21it looks like Juny was actually able to
- 3:07:24find a 3D model within our public folder
- 3:07:27and set up 3JS, React 3 Fiber, and React
- 3:07:313 Dre with Orbit controls to allow us to
- 3:07:34see this model. It messed up the
- 3:07:36positioning a bit, but that's totally
- 3:07:38okay because we can easily fix that. So,
- 3:07:40let's head over into the contact
- 3:07:42experience or create it for the first
- 3:07:44time if you haven't already and let's
- 3:07:46see what we have over here. As before, I
- 3:07:48found this model on SketchFab completely
- 3:07:51for free. So, you can download it and
- 3:07:53use it from here. But this time, I'll
- 3:07:56actually teach you another strategy of
- 3:07:58how you can autooptimize your models.
- 3:08:01Check this out. If you head over to the
- 3:08:04public folder and head over to models,
- 3:08:06you'll see that we have a computer
- 3:08:08optimized as well as the computer
- 3:08:11optimized
- 3:08:12transformed. If you rightclick it and
- 3:08:14open it within your file system, you'll
- 3:08:17notice that the computer optimized has
- 3:08:20486 kilobyt whereas the computer
- 3:08:23optimized transformed only has 40. That
- 3:08:27is more than a 10-fold optimization.
- 3:08:29Huge. So let me show you how you can
- 3:08:31achieve that. As before, we'll use this
- 3:08:35gltfjsx package, but this time we'll
- 3:08:38also apply a t flag which transforms the
- 3:08:41asset for the web. Draco prune resize.
- 3:08:45It'll automatically optimize the model.
- 3:08:47So within our code, just open up a
- 3:08:50terminal, cd into public, and then cd
- 3:08:54into models. Then run mpx gltf jsx
- 3:09:00computer-
- 3:09:05optimized.gb-t and press enter. You'll
- 3:09:08see that this will create a new computer
- 3:09:10optimized.jsx file. So move it into
- 3:09:13components and then
- 3:09:15models. Then head over into it to see
- 3:09:18what was generated and rename it over to
- 3:09:21computer.
- 3:09:24You'll see that it's getting the nodes
- 3:09:25and the materials by using the use gltf
- 3:09:29computer optimize transformed, but make
- 3:09:31sure to add forward/models right here
- 3:09:34before it. So, models computer
- 3:09:37optimized. And then we want to do the
- 3:09:39same thing right here at the bottom
- 3:09:42for/models/comput optimized. And now it
- 3:09:45has two different meshes to which we can
- 3:09:47also add some shadows. So, say cast
- 3:09:50shadows as well as receive shadows. This
- 3:09:54will have that special animated effect
- 3:09:56on our model. Now, head back over to the
- 3:10:00contact experience file and import that
- 3:10:03model. Even though what Juny created was
- 3:10:05totally working, I'll delete it just
- 3:10:07because we now optimized this 3D model
- 3:10:09even further. Maybe Juny would have been
- 3:10:11able to do that as well if I told it to.
- 3:10:14But for now, within this canvas, simply
- 3:10:16create a group.
- 3:10:18Give it a scale of like 0.03 because it
- 3:10:22is a big model. And let's change the
- 3:10:24position to something like 0 minus 1.5
- 3:10:29and then minus two. I found this
- 3:10:31position to work the best. And of
- 3:10:33course, let's say cast shadow. Within
- 3:10:36this group, you can render the computer
- 3:10:39model coming from components models. So
- 3:10:42in my case, if I go back to the
- 3:10:43application, I can now see this
- 3:10:45beautiful model right here with a lot of
- 3:10:48light and not too many shadows, at least
- 3:10:50not yet. But hey, it's here for you. It
- 3:10:54might not be there yet as we haven't
- 3:10:56added those additional things. But don't
- 3:10:58worry as we're going to do that right
- 3:11:00now. First, I want to look into orbit
- 3:11:03controls. We will set enable zoom to
- 3:11:06false and set the min polar angle to
- 3:11:10math pi over 5 and max polar angle to
- 3:11:14math pi / 2. This one limits how far the
- 3:11:18user can rotate down and this one limits
- 3:11:21how far the user can rotate up. So we
- 3:11:24don't want to allow them to go too much
- 3:11:25up or too much down because then the
- 3:11:27scene doesn't make any sense. And now of
- 3:11:29course we want to play with the lights.
- 3:11:31I'll remove this environment as we don't
- 3:11:33need it in this case. There we go. So
- 3:11:35now this looks a bit more dull, but we
- 3:11:38want to create a cozy and warm
- 3:11:41environment. So let's play with the
- 3:11:44ambient light with the intensity of
- 3:11:470.5, a color of hash
- 3:11:51FFF 4E6. That's going to give it that
- 3:11:54warm tone. Let's also play with a
- 3:11:56directional light of something like 553
- 3:12:00with an intensity of 2.5 and a color of
- 3:12:04hash
- 3:12:06FFD 9 B3. Okay, we're getting there. And
- 3:12:11instead of this spotlight, I'll add
- 3:12:14another directional light. This time
- 3:12:17with a position equal to 5
- 3:12:2291. There we go. Cast shadow will be
- 3:12:25turned on to true. Intensity will be
- 3:12:28also set to
- 3:12:292.5 and we'll add a color of hash FFD
- 3:12:359B3. These lights will add that warmth.
- 3:12:38And finally to actually cast shadows, we
- 3:12:41cannot do it in this universe because
- 3:12:43this is just a orange background, right?
- 3:12:46So it's not reflecting any shadows. To
- 3:12:49actually receive shadows, we need to
- 3:12:52create a large flat surface under the
- 3:12:55model so we have something to cast the
- 3:12:57shadows on. So right below this computer
- 3:13:02below the group create another group
- 3:13:05with a scale property of one one and a
- 3:13:11mesh that will receive
- 3:13:14shadows with a position of 0 minus 1.5
- 3:13:20and zero and a rotation
- 3:13:24of minus math.pi pi over two and then 0
- 3:13:290. I found those values to work the
- 3:13:32best. And within it, we can display the
- 3:13:35plane geometry. You can kind of see it
- 3:13:39right here at the bottom. So, we're
- 3:13:40basically adding the floor. I'll give it
- 3:13:42args of 3030, which is going to make it
- 3:13:46much larger, so we have more space to
- 3:13:49cast shadows. And I'll also give it a
- 3:13:51mesh standard
- 3:13:54material with a color of hash
- 3:13:58A46 B2D. Check that out. That's much
- 3:14:02better. And you can kind of see it in
- 3:14:03the distance right where it ends. But if
- 3:14:06you're just looking at the middle of the
- 3:14:07screen, you won't be able to tell the
- 3:14:08difference. But we still can't see any
- 3:14:10shadows, can we? Let's head over into
- 3:14:13our computer model to see if maybe we
- 3:14:16didn't cast the shadows somewhere. I'll
- 3:14:18actually zoom it in so we can see it
- 3:14:19better. Oh, I made a mistake. This was
- 3:14:22supposed to be cast shadow, not shadows
- 3:14:25because a mesh can only cast one shadow
- 3:14:27if it's a singular object and also
- 3:14:30receive shadow. So, you'll have to fix
- 3:14:32this on a couple of places. So, now that
- 3:14:34we know that that is good, we can also
- 3:14:36take a look at our lights and everything
- 3:14:37that we have within this canvas. This
- 3:14:39ambient light looks good to me. Besides,
- 3:14:43the light is maybe a bit too yellow. So
- 3:14:45let's say 4 E6. There we go. So now we
- 3:14:48have this more natural white color. And
- 3:14:50you can see the shadows are being
- 3:14:52applied on this computer right here,
- 3:14:54rather on the table. It's just they're
- 3:14:56not being applied on the floor. Looking
- 3:14:58at the directional light, we have a
- 3:15:00position of 553, intensity of 2.5, and
- 3:15:05some nice color. This is looking good.
- 3:15:07And another directional light, which is
- 3:15:09also looking good to me. Finally, we
- 3:15:11have a group for the computer with a
- 3:15:14scale and its position and it says cast
- 3:15:16shadow. Below it, we render the floor.
- 3:15:20So, here I see receive shadows. No, this
- 3:15:23was supposed to be receive shadow. There
- 3:15:25we go. So, now this scene really pops
- 3:15:28and comes to life because something in
- 3:15:31our minds is telling us, hey, this
- 3:15:34cannot be right. This is flying in the
- 3:15:36air. We need to see some shadows because
- 3:15:38the light is so bright. So there has to
- 3:15:40be
- 3:15:41shadow which just shows you how
- 3:15:43important shadows are. But with that in
- 3:15:45mind, we have now developed this get in
- 3:15:47touch with me session with a form as
- 3:15:50well as this nice looking 3D desk setup
- 3:15:54which has this some sort of a cartoonish
- 3:15:56look with nice shadows. So what do you
- 3:15:58say that in the next lesson we make the
- 3:16:00form functional so people looking your
- 3:16:03portfolio can actually send you emails.
- 3:16:08To make our form functional and to allow
- 3:16:10other users to email us, we'll use
- 3:16:13Email.js. It is completely free and
- 3:16:15super simple to set up. So, just head
- 3:16:18over to email.js. Create a free account
- 3:16:21or sign in if you have an account
- 3:16:23already. And once you're in, you should
- 3:16:25be able to see something that looks like
- 3:16:27this. Add a new service. Choose your
- 3:16:30email provider. In this case, I'll go
- 3:16:33with Gmail. Give it a name and copy the
- 3:16:36service ID. Head back over to your app
- 3:16:38and create a new env file. Within it,
- 3:16:43say
- 3:16:45vit_app email
- 3:16:49js_ervice ID and paste the service ID
- 3:16:52you just copied. Then click connect
- 3:16:55account and continue. It'll ask you
- 3:16:57whether you want to allow it to send
- 3:16:58emails on your behalf. Select yes. It'll
- 3:17:01say connected and click create service.
- 3:17:04Once the service has been added, head
- 3:17:06over to email templates. Create a new
- 3:17:09email template. You can leave it as a
- 3:17:11simple contact template. Click create.
- 3:17:14And you'll have to head over to settings
- 3:17:16and copy the template ID. Then within
- 3:17:19your env file, create another
- 3:17:22environment variable called v
- 3:17:26app.js template ID and simply paste the
- 3:17:29ID you just copied. Then head over to
- 3:17:31account within the sidebar and copy your
- 3:17:34public ID. This one you can add as vit
- 3:17:38app
- 3:17:39email.js public key. So you should have
- 3:17:43three keys in your env. Now back within
- 3:17:46our contact section. We'll have to
- 3:17:49somehow get access to the values within
- 3:17:51our form fields. And thankfully Juny has
- 3:17:54already done that for us. We have
- 3:17:56created a new use state with name,
- 3:18:00email, and message fields. And we're
- 3:18:02also handling the changes whenever user
- 3:18:04types anything on these fields. Just to
- 3:18:07make it a bit more responsive, we can
- 3:18:08also add the loading state. So, const
- 3:18:11loading set loading at the start set to
- 3:18:15use state false. And then we can update
- 3:18:18the button with the loading state. So,
- 3:18:20right below where we have our button,
- 3:18:22which is right here. The button can be
- 3:18:25disabled if we're currently loading. So,
- 3:18:27I'll say disabled if
- 3:18:30loading. And we can also change the text
- 3:18:33to say if we're loading, then display
- 3:18:36sending dot dot dot else display send
- 3:18:40message. Finally, we can head over to
- 3:18:43handle submit. First, you have to
- 3:18:46prevent the default behavior of the
- 3:18:47browser to reload once you submit a
- 3:18:49form. We don't want that within modern
- 3:18:52applications. And now that we have
- 3:18:53collected all of these values within
- 3:18:55form data, what do you say that we go
- 3:18:57ahead and hook it up to emailJS keys
- 3:19:00that we just got from their dashboard?
- 3:19:03To do that, you'll have to install a new
- 3:19:05package by running mpmi at
- 3:19:08email.js/browser
- 3:19:09js/b
- 3:19:12browser and then you can import it at
- 3:19:14the top by saying import
- 3:19:18email.js from at@
- 3:19:22email.js/b browser and then within
- 3:19:24handle submit we want to take the data
- 3:19:26that we have but we'll make it into an
- 3:19:29asynchronous function because sending an
- 3:19:31email does take some time. So instead of
- 3:19:34console logging, I'll just open up a try
- 3:19:37and catch block. In case something goes
- 3:19:40wrong, we'll get an error and I'll say
- 3:19:43console log email.js error. And maybe we
- 3:19:47can also display that error right here
- 3:19:49next to it. And then we can also add a
- 3:19:52finally block which will be executed no
- 3:19:54matter whether we succeed or fail with
- 3:19:56that request. And here we can just reset
- 3:19:59the loading to false because we want to
- 3:20:01stop it even when there's an error. And
- 3:20:04then in the try we want to use email.js
- 3:20:06functionality to send the form. First we
- 3:20:10will trigger the loading. So at the top
- 3:20:13I'll say set loading is set to true. And
- 3:20:17then here we'll say await
- 3:20:20email.js.end form. And you want to pass
- 3:20:22all the keys that we saved. import meta.
- 3:20:28env. App email.js service ID. That's the
- 3:20:33first one. You want to duplicate it. The
- 3:20:36second one is the template ID. So, how
- 3:20:39do you want your email to look like? And
- 3:20:41the third one is the public key. That's
- 3:20:45everything you need to send over an
- 3:20:47email. Finally, we want to reset the
- 3:20:50form after submission. So, I'll bring
- 3:20:53this up right here.
- 3:20:55And that's more or less it. This should
- 3:20:58successfully send an email. So what do
- 3:21:01you say that we go ahead and test it
- 3:21:03out? I've already filled in some
- 3:21:05information right here. But the question
- 3:21:06is where are we actually taking in this
- 3:21:09form data and passing it into the send
- 3:21:12form. See the way in which it works is
- 3:21:14first you pass the service ID, then the
- 3:21:17template ID, then the form, and finally
- 3:21:20the options. So right here after your
- 3:21:23second parameter, you want to pass in
- 3:21:25the form data. Specifically, you want to
- 3:21:28pass a reference to the form. So let's
- 3:21:31create a new reference right at the top
- 3:21:34by saying const form ref is equal to use
- 3:21:39ref coming from react at the start set
- 3:21:42to null. And we can attach this
- 3:21:44reference to a form. So just scroll down
- 3:21:47to where we start with our form and give
- 3:21:50this form a ref equal to form ref. And
- 3:21:54now right here we can pass formre
- 3:21:57ref.curren which will contain all the
- 3:21:59information from the form. So reload the
- 3:22:03app. Head over to the contact me. Let's
- 3:22:06say that your employer is contacting you
- 3:22:10from
- 3:22:12johndo.com and they're going to say
- 3:22:14you're
- 3:22:15hired. Finally, click send a message. It
- 3:22:19says
- 3:22:20sending. And what happened? It looks
- 3:22:23like I passed the wrong template ID. So,
- 3:22:25back on email.js, maybe I didn't proceed
- 3:22:28with fully completing my form. I'll
- 3:22:31click create. Oh yeah, I didn't save it.
- 3:22:34So, I'll have to head over to settings,
- 3:22:36copy the ID one more
- 3:22:38time, read it to my ENVs right here, and
- 3:22:44then don't forget to click save. Yep,
- 3:22:47this is what matters. Now, if you reload
- 3:22:50the page, scroll to the
- 3:22:52contact, and try to send out the email,
- 3:22:56the autofill also works. We're going to
- 3:22:58say you're hired and send the message.
- 3:23:02Sending. You can see that the form
- 3:23:05actually get cleared, which means that
- 3:23:06it successfully got sent. And if you
- 3:23:09head over to your email provider, you'll
- 3:23:11see a new email saying, "A message by
- 3:23:13Adrian has been received. Respond at
- 3:23:15your earliest convenience. You are
- 3:23:18hired." And the only problem that we
- 3:23:20have right now is that we don't know who
- 3:23:22to respond to. What is the email that
- 3:23:24they left? So if you head over to your
- 3:23:26template content alongside displaying
- 3:23:28the name and the message right below we
- 3:23:31can also render the email. So just use a
- 3:23:36double curly brace and render the email
- 3:23:38variable right here. Save it. And if we
- 3:23:41once again try to send out an email,
- 3:23:43this time on the big
- 3:23:46screen.
- 3:23:48Testing. The email arrives in a second.
- 3:23:51And now it also contains the email that
- 3:23:54you can respond to. Perfect. We've got
- 3:23:57it all covered. So to finish it off,
- 3:23:59let's also add a simple footer because
- 3:24:02this form and the 3D model really need
- 3:24:04some breathing room at the bottom of the
- 3:24:06screen.
- 3:24:08We'll do something super simple that
- 3:24:10looks like this where people can maybe
- 3:24:11read a bit more about us. I would
- 3:24:13suggest that you point this to your blog
- 3:24:15and then go ahead and implement it.
- 3:24:17Write about your learning journey
- 3:24:19because you don't really have terms and
- 3:24:21conditions. Also, put your professional
- 3:24:24profiles. Okay? I wouldn't actually
- 3:24:26advise going for Instagram or Facebook,
- 3:24:29but X or LinkedIn or the links to your
- 3:24:32blogs. Totally go for that. and then put
- 3:24:34your name here. To implement this
- 3:24:36footer, I'll head over into the sections
- 3:24:40folder and create a new file called
- 3:24:44footer.jsx, run rafce, and imported
- 3:24:48right within our app by simply rendering
- 3:24:50a self-closing footer component. We can
- 3:24:53then head over into it and wrap it in a
- 3:24:56semantic footer
- 3:24:58tag that'll have a class name of footer,
- 3:25:02which will automatically give it some
- 3:25:03room to breathe. Within it, we can
- 3:25:06render a div that'll have a class name
- 3:25:09equal to
- 3:25:13footer-container. And then within it,
- 3:25:15another div that'll have a class name of
- 3:25:19flex flex- call and justify center
- 3:25:24within which we can display an anchor
- 3:25:26tag with an href pointing to just
- 3:25:29forward slash.
- 3:25:31And this one can
- 3:25:33say visit my blog. So if you really want
- 3:25:37to take this portfolio further, try
- 3:25:39implementing a blog section as well or
- 3:25:41point to your medium blog maybe. Below
- 3:25:44that div, render another div. This one
- 3:25:47will have a class name of
- 3:25:50socials. And here you can map over your
- 3:25:53social images by saying social images
- 3:25:57map where you get each individual image.
- 3:26:00So I can say img and for each one render
- 3:26:04an anchor
- 3:26:05tag which will then render an image with
- 3:26:09a source of img img path. If you do that
- 3:26:15you should be able to see those four
- 3:26:17icons and these social images are coming
- 3:26:20from constants. So you can totally
- 3:26:22change their name or even delete or add
- 3:26:25some new platforms that you're using.
- 3:26:28And of course, put your URL right
- 3:26:31there. Let's give this anchor tag a
- 3:26:33class name equal to
- 3:26:37icon, a target of underscore blank, so
- 3:26:41it opens up within a new tab, an href
- 3:26:44pointing to the img. URL, and since
- 3:26:48we're mapping over it, also a key of img
- 3:26:52URL because it's unique. Finally, we can
- 3:26:54head below this div of socials and add
- 3:26:57another div for the right side of the
- 3:26:59footer with a class name of flex flex-
- 3:27:04call justify-c
- 3:27:06center within which we can have a p tag
- 3:27:11that'll have a class name equal to
- 3:27:14text-c center and on medium devices
- 3:27:18text- end and within it we can display
- 3:27:21that copyright sign and Then create a
- 3:27:24new date and get the current full year.
- 3:27:28So that way even the next year this will
- 3:27:30be updated. You can put your name right
- 3:27:34here and say something like all rights
- 3:27:37reserved. Well, I give you the rights
- 3:27:40right here to use this as a portfolio.
- 3:27:42Now we could also align this visit my
- 3:27:45blog to the center. So I'll say items
- 3:27:48center. There we go. So it looks good on
- 3:27:52mobile devices. But if we expand it, it
- 3:27:55gives just the right amount of space
- 3:27:57right here for desktop devices too. But
- 3:28:00here I actually want to keep it on the
- 3:28:02left
- 3:28:03corner. So I can say on medium devices
- 3:28:06and larger items
- 3:28:08start, which is going to put it here,
- 3:28:11but on mobile it'll be centered. And
- 3:28:14that brings us to the end of the footer
- 3:28:16and almost to the end of the entire
- 3:28:18build. One thing that I don't like right
- 3:28:20now is that we haven't changed the
- 3:28:21favicon nor the title. I want it to look
- 3:28:24something a bit more like this. So to
- 3:28:27change it, head over to the
- 3:28:29index.html file right here and change
- 3:28:33the title to something like your name
- 3:28:36and m dash semicolon. Do two of these
- 3:28:40and then say
- 3:28:42portfolio. This will form this nice
- 3:28:45Adrian longline portfolio. Also, we just
- 3:28:48have to replace this favicon, which is
- 3:28:50the default V1, which everybody has,
- 3:28:53with the updated favicon. So, point the
- 3:28:55path of the link to forward slash
- 3:29:00images/fav.png. If you save it, you'll
- 3:29:03see my initials right here. But, of
- 3:29:05course, I don't want you to use my
- 3:29:07initials. So on the right side of the
- 3:29:10design, I've prepared this part right
- 3:29:12here that says icons where you can
- 3:29:14change some of the logos or some other
- 3:29:16aspects of your portfolio. For example,
- 3:29:19you can change your initials right here.
- 3:29:21So to be able to change it, you'll have
- 3:29:23to duplicate it. So just say duplicate
- 3:29:26to your drafts and then you'll be able
- 3:29:28to edit and export this icon. Once you
- 3:29:32do that, you can just put it to your
- 3:29:33public folder, point to it right here,
- 3:29:36and then it'll update for your
- 3:29:37portfolio. And with that said, we are
- 3:29:40done with building our amazing
- 3:29:42portfolio. So, the last step we have to
- 3:29:45do is to get it deployed to the internet
- 3:29:47so we can share it with
- 3:29:50everyone. And we're back on Hostinger's
- 3:29:53dashboard. I already got the domain name
- 3:29:56with my hosting. And if you purchased
- 3:29:58and selected yours, you should be able
- 3:30:00to see a page that looks something like
- 3:30:01this. If you didn't get the hosting or
- 3:30:03the domain name yet, you can do that by
- 3:30:05clicking the link down in the
- 3:30:06description. Once you find your website
- 3:30:08or your domain name, click file manager.
- 3:30:11Then head over into your public HTML and
- 3:30:14delete the default PHP because here
- 3:30:17we're going to upload our website. So go
- 3:30:19back to your application, stop it from
- 3:30:22running and close all the terminals and
- 3:30:24just run mpm run
- 3:30:28build. This will build an optimized
- 3:30:30production build of your application.
- 3:30:32There we go. Took just 3 seconds. If you
- 3:30:35check out your project, you'll notice
- 3:30:36that there's a new dist as in
- 3:30:38distribution folder that contains
- 3:30:41everything about your website. So
- 3:30:43rightclick it and open it up within your
- 3:30:45finder or file explorer. Then go into
- 3:30:48it, select everything within it, and
- 3:30:50drag and drop it right here within your
- 3:30:52H panel. There we go. Now close the H
- 3:30:55panel. You'll be redirected back to your
- 3:30:58domain and click on it. And that's it.
- 3:31:01It loaded instantly with the proper
- 3:31:03favicon title and even an HTTPS
- 3:31:08certificate. Just try reloading it and
- 3:31:10see how fast it is. And take a second to
- 3:31:14admire everything you've built. Having
- 3:31:16this custom domain name, SSL certificate
- 3:31:19and fast load times definitely makes you
- 3:31:22stand out in the crowd. And let's also
- 3:31:24test out our email
- 3:31:25functionality test from
- 3:31:28production. I'll go ahead and send it.
- 3:31:31And I got it in a second. Perfect. And
- 3:31:34there you have it. You have successfully
- 3:31:36built your own 3D personal
- 3:31:40portfolio. Congratulations. Oh yeah, and
- 3:31:43let's not forget that it is fully mobile
- 3:31:46responsive. I mean, just look at it with
- 3:31:49these nice looking timeline
- 3:31:51animations. It is just amazing. Since
- 3:31:55you came to the end of this one, I know
- 3:31:56you love 3JS and I know you want to get
- 3:31:59better at it. So, the only next step
- 3:32:01that makes sense for you right now after
- 3:32:03watching this video is to check out the
- 3:32:06ultimate 3GS course live right now on
- 3:32:09GSMy Pro. Here we'll dive super deep
- 3:32:13into every single 3JS property that we
- 3:32:16covered in this video and so much more
- 3:32:20from different geometries, planes,
- 3:32:22modifying the 3D world and lights and
- 3:32:25shadows and so much more. We'll explore
- 3:32:27everything that 3JS has to offer. So
- 3:32:30check it out. The link is in the
- 3:32:32description. With that said, thank you
- 3:32:34so much for watching and I'll see you on
- 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.