A dead simple time tracker that keeps everything in local storage. Next.js, no accounts and no server.
handcodedlocalstoragenextjsreactself-hostedserverlesstime-trackingtimer
1.time {
2 font-size: 8em;
3 text-align: center;
4 font-weight: 300;
5 font-variant-numeric: tabular-nums;
6 letter-spacing: 0.02em;
7 color: var(--green-bright);
8 text-shadow: 0 0 24px var(--green-glow);
9 margin-bottom: 1rem;
10 position: relative;
11}
12
13.time::before,
14.time::after {
15 content: "";
16 display: block;
17 width: 80px;
18 height: 1px;
19 margin: 0.5rem auto;
20 background: linear-gradient(90deg, transparent, var(--green) 50%, transparent);
21 opacity: 0.7;
22}
23
24@media (max-width: 1023.99px) {
25 .time {
26 font-size: 3.8em;
27 }
28 .time::before,
29 .time::after {
30 width: 50px;
31 }
32}
33
34.inputs {
35 text-align: center;
36 margin-top: 1.5rem;
37}
38
39.noteWrap {
40 display: inline-block;
41 text-align: left;
42 max-width: 100%;
43}
44
45.note {
46 width: 560px;
47 max-width: calc(100vw - 80px);
48 text-align: center;
49 border: 1px solid var(--border-green);
50 background: var(--surface);
51 color: var(--text);
52 padding: 14px 20px;
53 font-size: 0.95em;
54 letter-spacing: 0.04em;
55 border-radius: var(--radius);
56 transition:
57 border-color 200ms ease,
58 box-shadow 200ms ease,
59 background 200ms ease;
60}
61
62.note:hover {
63 border-color: var(--border-green-hover);
64}
65
66.note:focus {
67 outline: none;
68 border-color: var(--green);
69 background: var(--surface-2);
70 box-shadow: 0 0 0 3px var(--green-dim);
71}
72
73.note::placeholder {
74 font-size: 0.85em;
75 text-transform: uppercase;
76 font-weight: 500;
77 letter-spacing: 0.15em;
78 color: var(--text-dim);
79}
80
81@media (max-width: 1023.99px) {
82 .note {
83 padding: 12px 16px;
84 font-size: 0.9em;
85 width: 300px;
86 }
87}
88
89.buttons {
90 display: flex;
91 gap: 12px;
92 justify-content: center;
93 margin-top: 16px;
94 width: 560px;
95 max-width: calc(100vw - 80px);
96 margin-left: auto;
97 margin-right: auto;
98}
99
100.button {
101 flex: 1;
102 padding: 12px 20px;
103 font-size: 0.78em;
104 letter-spacing: 0.16em;
105 cursor: pointer;
106 text-transform: uppercase;
107 font-weight: 600;
108 background: transparent;
109 border-radius: var(--radius);
110 transition:
111 background 180ms ease,
112 color 180ms ease,
113 border-color 180ms ease,
114 box-shadow 180ms ease;
115}
116
117.button:focus-visible {
118 outline: none;
119 box-shadow: 0 0 0 3px var(--green-dim);
120}
121
122@media (max-width: 1023.99px) {
123 .buttons {
124 width: 300px;
125 }
126 .button {
127 padding: 11px 16px;
128 font-size: 0.72em;
129 letter-spacing: 0.12em;
130 }
131}
132
133.timePaused {
134 color: var(--amber-bright);
135 text-shadow: 0 0 24px rgba(201, 168, 76, 0.35);
136}
137
138.pausedBadge {
139 position: absolute;
140 right: 50%;
141 transform: translateX(50%);
142 top: -6px;
143 font-size: 0.12em;
144 letter-spacing: 0.32em;
145 font-weight: 600;
146 text-transform: uppercase;
147 color: var(--amber-bright);
148 background: var(--amber-dim);
149 border: 1px solid rgba(201, 168, 76, 0.3);
150 padding: 4px 10px;
151 border-radius: 2px;
152 text-shadow: none;
153}
154
155.pauseButton {
156 color: var(--amber-bright);
157 border: 1px solid rgba(201, 168, 76, 0.35);
158 background: var(--amber-dim);
159}
160
161.pauseButton:hover {
162 color: var(--amber-bright);
163 background: rgba(201, 168, 76, 0.18);
164 border-color: var(--amber-bright);
165}
166
167.resetButton {
168 color: var(--text-muted);
169 border: 1px solid var(--border);
170}
171
172.resetButton:hover {
173 color: var(--terracotta-bright);
174 border-color: var(--terracotta);
175 background: var(--terracotta-dim);
176}
177
178.addButton {
179 color: var(--green-bright);
180 border: 1px solid var(--green);
181 background: var(--green-dim);
182}
183
184.addButton:hover {
185 color: var(--green-hi);
186 background: rgba(107, 158, 120, 0.22);
187 border-color: var(--green-bright);
188 box-shadow: 0 0 12px var(--green-glow);
189}