Animation-heavy personal portfolio on Next.js: a custom cursor, a page-transition loader, and canvas backgrounds.
canvas-animationscss-modulesdark-themehandcodednextjspersonal-websiteportfolioreact
1@keyframes fadeStart {
2 from {
3 opacity: 0;
4 }
5 to {
6 opacity: 1;
7 }
8}
9
10@keyframes slideUp {
11 from {
12 opacity: 0;
13 transform: translateY(30px);
14 }
15 to {
16 opacity: 1;
17 transform: translateY(0);
18 }
19}
20
21.imageWrapper {
22 position: fixed;
23 top: 0;
24 left: 0;
25 right: 0;
26 bottom: 0;
27 width: 100%;
28 height: 100vh;
29 z-index: 0;
30 pointer-events: none;
31}
32
33.words {
34 position: fixed;
35 top: 0;
36 bottom: 0;
37 left: 0;
38 right: 0;
39 width: 100%;
40 height: 100vh;
41 display: flex;
42 align-items: center;
43 justify-content: center;
44 z-index: 10;
45 mix-blend-mode: darken;
46 background: rgba(0, 0, 0, 0.8);
47}
48
49.word {
50 opacity: 0.2;
51 font-size: 11vw;
52 text-transform: uppercase;
53 margin: 0;
54 position: absolute;
55}
56
57.wordAppear,
58.wordEnter {
59 opacity: 0;
60 transform: translateY(-200px);
61}
62
63.wordAppearActive,
64.wordEnterActive {
65 opacity: 0.2;
66 transform: translateY(0);
67 transition-duration: 1000ms;
68 transition-property: opacity, transform;
69}
70
71.wordAppearDone,
72.wordEnterDone {
73 opacity: 0.2;
74}
75
76.wordExit {
77 transform: translateY(0);
78 opacity: 0.2;
79}
80
81.wordExitActive {
82 opacity: 0;
83 transform: translateY(200px);
84 transition-duration: 1000ms;
85 transition-property: opacity, transform;
86}
87
88.hero {
89 position: relative;
90 z-index: 20;
91 opacity: 0;
92 animation-name: fadeStart;
93 animation-duration: 1500ms;
94 animation-fill-mode: forwards;
95 animation-play-state: paused;
96}
97
98.hero::before {
99 content: "";
100 display: block;
101 width: 60px;
102 height: 4px;
103 margin-bottom: 24px;
104 background-image: linear-gradient(
105 to right,
106 var(--color-blue) 0,
107 var(--color-purple) 100%
108 );
109}
110
111.name {
112 margin: 0 0 16px 0;
113 line-height: 0.9;
114 overflow: visible;
115}
116
117.firstName {
118 display: block;
119 font-size: clamp(2.8em, 6.5vw, 5em);
120 font-weight: 800;
121 text-transform: uppercase;
122 letter-spacing: -0.02em;
123}
124
125.lastName {
126 display: block;
127 font-size: clamp(2.8em, 6.5vw, 5em);
128 font-weight: 800;
129 text-transform: uppercase;
130 letter-spacing: -0.02em;
131 background-image: linear-gradient(
132 to right,
133 var(--color-blue) 0,
134 var(--color-purple) 100%
135 );
136 -webkit-background-clip: text;
137 background-clip: text;
138 -webkit-text-fill-color: transparent;
139 filter: drop-shadow(0 0 20px rgba(0, 0, 0, 0.8));
140}
141
142.role {
143 display: flex;
144 align-items: baseline;
145 gap: 10px;
146 font-family: monospace;
147 font-size: 1.5em;
148 letter-spacing: 2px;
149 text-transform: uppercase;
150}
151
152.roleTitle {
153 color: white;
154 font-weight: 700;
155}
156
157.roleAt {
158 color: rgba(255, 255, 255, 0.4);
159 font-weight: 400;
160}
161
162.roleCompany {
163 color: rgba(255, 255, 255, 0.6);
164 font-weight: 400;
165}
166
167.notice {
168 display: block;
169 margin-top: 32px;
170 padding: 16px 20px;
171 border-left: 3px solid var(--color-purple);
172 background: rgba(0, 0, 0, 0.5);
173 text-decoration: none;
174 transition: background 250ms ease, border-color 250ms ease;
175 max-width: 420px;
176}
177
178.notice:hover {
179 background: rgba(0, 0, 0, 0.65);
180 border-color: var(--color-blue);
181}
182
183.noticeLabel {
184 display: inline-block;
185 font-family: monospace;
186 font-size: 0.85em;
187 font-weight: 700;
188 text-transform: uppercase;
189 letter-spacing: 3px;
190 color: rgba(255, 255, 255, 0.6);
191 margin-bottom: 6px;
192}
193
194.noticeTitle {
195 display: block;
196 font-size: 1.3em;
197 font-weight: 800;
198 text-transform: uppercase;
199 letter-spacing: 1px;
200 color: white;
201 margin-bottom: 4px;
202}
203
204.noticeDesc {
205 display: block;
206 font-size: 0.85em;
207 line-height: 1.5;
208 color: rgba(255, 255, 255, 0.7);
209 font-style: italic;
210}
211
212@media (max-width: 767.98px) {
213 .firstName,
214 .lastName {
215 font-size: 10vw;
216 }
217
218 .role {
219 flex-direction: column;
220 gap: 2px;
221 font-size: 0.85em;
222 }
223
224 .notice {
225 margin-top: 20px;
226 padding: 10px 14px;
227 }
228
229 .noticeLabel {
230 font-size: 0.6em;
231 letter-spacing: 2px;
232 }
233
234 .noticeTitle {
235 font-size: 0.9em;
236 }
237
238 .noticeDesc {
239 font-size: 0.75em;
240 }
241}