Animation-heavy personal portfolio on Next.js: a custom cursor, a page-transition loader, and canvas backgrounds.
canvas-animationscss-modulesdark-themehandcodednextjspersonal-websiteportfolioreact
1@keyframes transformRight {
2 from {
3 transform: scaleX(0);
4 }
5 to {
6 transform: scaleX(1);
7 }
8}
9
10@keyframes transformLeft {
11 from {
12 transform: scaleX(1);
13 }
14 to {
15 transform: scaleX(0);
16 }
17}
18
19@keyframes cardEnter {
20 from {
21 opacity: 0;
22 transform: translateY(20px);
23 }
24 to {
25 opacity: 1;
26 transform: translateY(0);
27 }
28}
29
30.background {
31 position: absolute;
32 top: 0;
33 left: 0;
34 right: 0;
35 bottom: 0;
36 background-color: white;
37 z-index: -2;
38}
39
40.heading {
41 font-weight: 700;
42 margin-top: 60px;
43 margin-bottom: 20px;
44 font-size: 2.5em;
45 color: black;
46}
47
48.heading::before {
49 content: "";
50 display: block;
51 width: 50px;
52 height: 5px;
53 margin-bottom: 20px;
54 background-color: var(--color-blue);
55}
56
57.paragraph {
58 font-size: 1.5em;
59 margin-top: 0;
60 margin-bottom: 30px;
61 font-weight: 300;
62 color: black;
63}
64
65.paragraph a {
66 color: black;
67 text-decoration: none;
68 position: relative;
69 white-space: nowrap;
70}
71
72.paragraph a::before {
73 content: "";
74 position: absolute;
75 left: 0;
76 right: 0;
77 bottom: 0;
78 height: 2px;
79 background: rgba(0, 0, 0, 1);
80 transform-origin: left;
81 animation: transformLeft 300ms normal forwards;
82}
83
84.paragraph a::after {
85 content: "";
86 position: absolute;
87 left: 0;
88 right: 0;
89 bottom: 0;
90 height: 2px;
91 background: rgba(0, 0, 0, 0.2);
92}
93
94.paragraph a:hover::before {
95 animation: transformRight 300ms normal forwards;
96}
97
98.projects {
99 display: flex;
100 width: 100%;
101 flex-wrap: wrap;
102 gap: 20px;
103 padding-bottom: 60px;
104}
105
106.project {
107 width: calc(50% - 10px);
108 display: flex;
109 flex-direction: column;
110 animation: cardEnter 600ms cubic-bezier(0.16, 1, 0.3, 1) both;
111}
112
113@media (max-width: 767.98px) {
114 .project {
115 width: 100%;
116 }
117}
118
119.projectHeading {
120 font-weight: 700;
121 margin-top: 40px;
122 margin-bottom: 8px;
123 font-size: 2em;
124 color: black;
125}
126
127.tags {
128 display: flex;
129 flex-wrap: wrap;
130 gap: 6px;
131 margin-bottom: 12px;
132}
133
134.tag {
135 display: inline-block;
136 font-size: 0.75em;
137 padding: 2px 8px;
138 background-color: var(--color-blue);
139 border-radius: var(--radius-2);
140 color: white;
141 font-family: monospace;
142 text-transform: uppercase;
143 letter-spacing: 1px;
144}
145
146.projectParagraph {
147 font-size: 1.3em;
148 margin-top: 0;
149 margin-bottom: 16px;
150 font-weight: 300;
151 color: black;
152 flex-grow: 1;
153 line-height: 1.5;
154}
155
156.projectCommit {
157 font-family: monospace;
158 background: var(--color-primary);
159 color: #00ff00;
160 padding: 16px;
161 overflow-x: hidden;
162 max-width: 100%;
163 text-overflow: ellipsis;
164 font-size: 0.85em;
165 line-height: 1.6;
166 border-radius: var(--radius-2);
167 margin-top: 0;
168 margin-bottom: 16px;
169}
170
171.projectButton {
172 padding: 8px 12px;
173 font-size: 0.9em;
174 text-decoration: none;
175 background-color: black;
176 font-weight: 700;
177 text-transform: uppercase;
178 display: inline-flex;
179 align-items: center;
180 justify-content: center;
181 letter-spacing: 2px;
182 color: white;
183 background-image: linear-gradient(
184 to right,
185 var(--color-blue) 0,
186 var(--color-purple) 100%
187 );
188 transform: scale(1);
189 transition-duration: 250ms;
190 transition-property: transform;
191 width: 120px;
192 text-align: center;
193 border-radius: var(--radius-2);
194}
195
196.projectButton svg {
197 margin-right: 10px;
198}
199
200.projectButton:hover {
201 transform: scale(1.2);
202}