Animation-heavy personal portfolio on Next.js: a custom cursor, a page-transition loader, and canvas backgrounds.
canvas-animationscss-modulesdark-themehandcodednextjspersonal-websiteportfolioreact
1@keyframes foldUp {
2 0% {
3 height: 100vh;
4 }
5 100% {
6 height: 0;
7 }
8}
9
10@keyframes dotPulse {
11 0%,
12 80%,
13 100% {
14 opacity: 0.2;
15 transform: scale(0.8);
16 }
17 40% {
18 opacity: 1;
19 transform: scale(1);
20 }
21}
22
23.grid {
24 display: grid;
25 grid-template-columns: 60px 15% 1fr 1fr 15% 60px;
26 grid-template-rows: 1fr;
27 min-height: 100vh;
28 width: 100%;
29 position: fixed;
30 z-index: 9999;
31 pointer-events: none;
32}
33
34.indicator {
35 position: fixed;
36 top: 50%;
37 left: 50%;
38 transform: translate(-50%, -50%);
39 display: flex;
40 gap: 8px;
41 opacity: 0;
42 transition: opacity 400ms ease-in-out;
43 z-index: 10000;
44 pointer-events: none;
45}
46
47.indicator[data-ready="false"] {
48 opacity: 1;
49 transition-delay: 250ms;
50}
51
52.dot {
53 width: 8px;
54 height: 8px;
55 border-radius: 50%;
56 background-color: white;
57 animation: dotPulse 1400ms ease-in-out infinite both;
58}
59
60.dot:nth-child(2) {
61 animation-delay: 160ms;
62}
63
64.dot:nth-child(3) {
65 animation-delay: 320ms;
66}
67
68.gridColumn::before,
69.gridColumn::after {
70 content: "";
71 display: block;
72 width: 100%;
73 height: 100vh;
74 animation: foldUp 1000ms normal forwards;
75 animation-delay: var(--delay, 0ms);
76 animation-play-state: var(--play-state, running);
77}
78
79.gridColumn::before {
80 background-color: var(--color-primary);
81 border-right: 1px solid var(--color-primary);
82}
83
84.gridColumn::after {
85 background-color: white;
86 border-right: 1px solid white;
87}