A dead simple time tracker that keeps everything in local storage. Next.js, no accounts and no server.
handcodedlocalstoragenextjsreactself-hostedserverlesstime-trackingtimer
1@keyframes fadeRight {
2 from {
3 opacity: 0;
4 transform: translateX(-40px);
5 }
6 to {
7 opacity: 1;
8 transform: translateX(0);
9 }
10}
11
12.githubLink {
13 position: absolute;
14 top: 24px;
15 right: 24px;
16 text-decoration: none;
17 z-index: 5;
18}
19
20.githubLogo {
21 width: 32px;
22 height: 32px;
23 color: var(--text-muted);
24 opacity: 0.6;
25 transition:
26 opacity 200ms ease,
27 color 200ms ease,
28 transform 200ms ease;
29}
30
31.githubLink:hover .githubLogo {
32 opacity: 1;
33 color: var(--green-bright);
34 transform: rotate(6deg);
35}
36
37.blockquote {
38 font-size: 1.35em;
39 margin: 4rem auto 3.5rem auto;
40 padding: 0 2rem;
41 position: relative;
42 max-width: 520px;
43 color: var(--text);
44 line-height: 1.6;
45 letter-spacing: 0.01em;
46 opacity: 0;
47 animation: fadeRight 600ms ease-out forwards;
48}
49
50.blockquote::before {
51 content: "”";
52 position: absolute;
53 top: -1.5rem;
54 right: 0;
55 font-size: 6em;
56 opacity: 0.12;
57 line-height: 0.5em;
58 color: var(--green-bright);
59 pointer-events: none;
60}
61
62@media (max-width: 1023.99px) {
63 .blockquote {
64 font-size: 1.05em;
65 margin: 2.5rem auto 2.5rem auto;
66 padding: 0 1rem;
67 }
68 .blockquote::before {
69 font-size: 4em;
70 }
71}
72
73.creator {
74 display: block;
75 margin-left: auto;
76 margin-right: 2rem;
77 margin-bottom: 4rem;
78 width: fit-content;
79 font-size: 1.25em;
80 color: var(--text-bright);
81 text-decoration: none;
82 transition: color 200ms ease;
83 position: relative;
84 opacity: 0;
85 animation: fadeRight 600ms ease-out 300ms forwards;
86 padding-bottom: 6px;
87}
88
89.creator::after {
90 content: "";
91 position: absolute;
92 left: 0;
93 right: 0;
94 bottom: 0;
95 height: 2px;
96 background: var(--green);
97 opacity: 0.25;
98 pointer-events: none;
99}
100
101.creator::before {
102 content: "";
103 position: absolute;
104 left: 0;
105 right: 0;
106 bottom: 0;
107 height: 2px;
108 background: var(--green);
109 transform-origin: left;
110 transform: scaleX(1);
111 animation: scaleLeftOut 400ms ease forwards;
112 pointer-events: none;
113}
114
115.creator:hover {
116 color: var(--green-bright);
117}
118
119.creator:hover::before {
120 animation: scaleRightIn 300ms ease forwards;
121}
122
123@keyframes scaleLeftOut {
124 from {
125 transform: scaleX(1);
126 transform-origin: right;
127 }
128 to {
129 transform: scaleX(0);
130 transform-origin: right;
131 }
132}
133
134@keyframes scaleRightIn {
135 from {
136 transform: scaleX(0);
137 transform-origin: left;
138 }
139 to {
140 transform: scaleX(1);
141 transform-origin: left;
142 }
143}
144
145@media (max-width: 1023.99px) {
146 .creator {
147 font-size: 1.05em;
148 }
149}
150
151.descriptionContainer {
152 display: grid;
153 grid-template-columns: 1fr 1fr;
154 gap: 3rem;
155 margin-top: 1rem;
156}
157
158@media (max-width: 1023.99px) {
159 .descriptionContainer {
160 grid-template-columns: 1fr;
161 gap: 1.5rem;
162 }
163}
164
165.keysColumn {
166 display: flex;
167 flex-direction: column;
168 gap: 6px;
169}
170
171.keysDescription {
172 font-size: 0.88em;
173 color: var(--text);
174 display: flex;
175 align-items: center;
176 gap: 12px;
177 margin: 0;
178 padding: 4px 0;
179 opacity: 0;
180 animation: fadeRight 600ms ease-out 400ms forwards;
181}
182
183.keysDescription span {
184 min-width: 58px;
185 text-align: center;
186 padding: 3px 8px;
187 background: var(--surface);
188 border: 1px solid var(--border-green);
189 border-radius: 2px;
190 font-family: var(--font-mono);
191 font-size: 0.85em;
192 color: var(--green-bright);
193 letter-spacing: 0.04em;
194}
195
196@media (max-width: 1023.99px) {
197 .keysDescription {
198 font-size: 0.82em;
199 }
200}
201
202.languageRow {
203 margin-top: 0.5rem;
204 margin-bottom: 2rem;
205}