4sp-dv 1.0.24 → 1.0.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/4simpleproblems_v5.html +2 -2
- package/logged-in/dictionary.html +7 -1
- package/logged-in/weather.html +187 -167
- package/package.json +1 -1
package/4simpleproblems_v5.html
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<title>4SP - VERSION 5 CLIENT</title>
|
|
7
7
|
<link rel="icon" type="image/png" href="https://cdn.jsdelivr.net/npm/4sp-dv@latest/images/logo.png">
|
|
8
8
|
|
|
9
|
-
<base href="https://cdn.jsdelivr.net/npm/4sp-dv@1.0.
|
|
9
|
+
<base href="https://cdn.jsdelivr.net/npm/4sp-dv@1.0.25/logged-in/">
|
|
10
10
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
11
11
|
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap" rel="stylesheet">
|
|
12
12
|
|
|
@@ -774,7 +774,7 @@
|
|
|
774
774
|
const displayUsername = document.getElementById('display-username');
|
|
775
775
|
const displayEmail = document.getElementById('display-email');
|
|
776
776
|
|
|
777
|
-
const BASE_URL = 'https://cdn.jsdelivr.net/npm/4sp-dv@1.0.
|
|
777
|
+
const BASE_URL = 'https://cdn.jsdelivr.net/npm/4sp-dv@1.0.25/logged-in/';
|
|
778
778
|
const STORAGE_KEY = 'local_access_code';
|
|
779
779
|
const USER_DATA_KEY = 'local_user_data';
|
|
780
780
|
const LAST_PAGE_KEY = 'local_last_page';
|
|
@@ -349,7 +349,13 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
349
349
|
function updateUrlHash(word) {
|
|
350
350
|
const newHash = word ? `#word=${encodeURIComponent(word.toLowerCase())}` : '';
|
|
351
351
|
if (window.location.hash === newHash) return;
|
|
352
|
-
|
|
352
|
+
try {
|
|
353
|
+
history.pushState(null, '', newHash);
|
|
354
|
+
} catch (e) {
|
|
355
|
+
// Ignore SecurityError when running via file:// protocol
|
|
356
|
+
// or fallback to basic hash assignment if needed
|
|
357
|
+
window.location.hash = newHash;
|
|
358
|
+
}
|
|
353
359
|
}
|
|
354
360
|
|
|
355
361
|
function checkUrlHashForWord() {
|
package/logged-in/weather.html
CHANGED
|
@@ -11,134 +11,175 @@
|
|
|
11
11
|
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap" rel="stylesheet">
|
|
12
12
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
|
|
13
13
|
|
|
14
|
-
<!-- FontAwesome 6.5.2 (Free CDN) -->
|
|
15
14
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css">
|
|
15
|
+
|
|
16
16
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
17
17
|
<script src="../navigation.js"></script>
|
|
18
18
|
<script src="../injector.js"></script>
|
|
19
19
|
|
|
20
20
|
<style>
|
|
21
|
-
/* --- 4SP BASE STYLING --- */
|
|
21
|
+
/* --- 4SP BASE STYLING (MATCHING notes.html) --- */
|
|
22
22
|
:root {
|
|
23
|
+
--bg-page: #040404;
|
|
24
|
+
--bg-container: #000000;
|
|
25
|
+
--border-color: #333;
|
|
26
|
+
--text-primary: #c0c0c0;
|
|
27
|
+
--color-indigo: #4f46e5;
|
|
23
28
|
--menu-bg: #000000;
|
|
24
29
|
--menu-border: #333;
|
|
25
|
-
--menu-text: #d1d5db;
|
|
26
|
-
--menu-item-hover-bg: #2a2a2a;
|
|
27
|
-
--accent-bg: rgba(79, 70, 229, 0.1);
|
|
28
|
-
--accent-border: #4f46e5;
|
|
29
|
-
--accent-text: #6366f1;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
body {
|
|
33
33
|
font-family: 'Geist', sans-serif;
|
|
34
|
-
background-color:
|
|
35
|
-
color:
|
|
34
|
+
background-color: var(--bg-page);
|
|
35
|
+
color: var(--text-primary);
|
|
36
36
|
transition: all 0.3s ease;
|
|
37
37
|
font-size: 16px;
|
|
38
38
|
overflow-x: hidden;
|
|
39
39
|
font-weight: 300;
|
|
40
|
+
min-height: 100vh;
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
40
43
|
}
|
|
41
44
|
|
|
42
45
|
h1, h2, h3, .font-bold, .font-semibold, strong {
|
|
43
|
-
font-weight: 400 !important;
|
|
46
|
+
font-weight: 400 !important;
|
|
44
47
|
}
|
|
45
48
|
|
|
46
|
-
/* ---
|
|
47
|
-
.btn-toolbar {
|
|
48
|
-
background: var(--menu-bg);
|
|
49
|
-
border: 1px solid var(--menu-border);
|
|
49
|
+
/* --- UI COMPONENTS (MATCHING notes.html) --- */
|
|
50
|
+
.btn-toolbar-style {
|
|
51
|
+
background: var(--menu-bg, #000000);
|
|
52
|
+
border: 1px solid var(--menu-border, #333);
|
|
50
53
|
border-radius: 0.75rem;
|
|
51
|
-
color:
|
|
54
|
+
color: #d1d5db;
|
|
52
55
|
padding: 0.5rem 1rem;
|
|
53
56
|
font-weight: 500;
|
|
54
57
|
cursor: pointer;
|
|
55
58
|
transition: all 0.2s;
|
|
56
59
|
display: inline-flex;
|
|
57
60
|
align-items: center;
|
|
61
|
+
justify-content: center;
|
|
58
62
|
gap: 0.5rem;
|
|
59
|
-
font-size: 0.9rem;
|
|
60
63
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
color: #fff;
|
|
64
|
+
|
|
65
|
+
.btn-toolbar-style:hover {
|
|
66
|
+
background-color: #000000;
|
|
67
|
+
border-color: #fff;
|
|
68
|
+
color: #ffffff;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.btn-toolbar-style.btn-primary-override {
|
|
72
|
+
background-color: rgba(79, 70, 229, 0.1);
|
|
73
|
+
border: 1px solid #4f46e5;
|
|
74
|
+
color: #4f46e5;
|
|
65
75
|
}
|
|
66
|
-
.btn-toolbar.
|
|
67
|
-
background-color:
|
|
68
|
-
border-color:
|
|
69
|
-
color:
|
|
76
|
+
.btn-toolbar-style.btn-primary-override:hover {
|
|
77
|
+
background-color: rgba(79, 70, 229, 0.15);
|
|
78
|
+
border-color: #6366f1;
|
|
79
|
+
color: #6366f1;
|
|
70
80
|
}
|
|
71
81
|
|
|
72
|
-
/*
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
border
|
|
77
|
-
color: #
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
font-weight: 400;
|
|
81
|
-
width: 260px;
|
|
82
|
+
/* Input Styling */
|
|
83
|
+
.input-text-style {
|
|
84
|
+
width: 100%;
|
|
85
|
+
padding: 0.6rem 1rem;
|
|
86
|
+
border: 1px solid #333;
|
|
87
|
+
background-color: #000000;
|
|
88
|
+
border-radius: 0.75rem;
|
|
89
|
+
color: #c0c0c0;
|
|
82
90
|
transition: all 0.2s;
|
|
91
|
+
font-size: 0.95rem;
|
|
92
|
+
font-weight: 300;
|
|
93
|
+
}
|
|
94
|
+
.input-text-style:focus {
|
|
95
|
+
border-color: #505050;
|
|
83
96
|
outline: none;
|
|
97
|
+
box-shadow: none;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* --- LAYOUT UTILS --- */
|
|
101
|
+
#page-content {
|
|
102
|
+
flex-grow: 1;
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-direction: column;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
header {
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
gap: 1rem;
|
|
111
|
+
padding: 1.5rem;
|
|
112
|
+
border-bottom: 1px solid #1a1a1a;
|
|
113
|
+
background-color: var(--bg-page);
|
|
84
114
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
115
|
+
@media(min-width: 768px) {
|
|
116
|
+
header {
|
|
117
|
+
flex-direction: row;
|
|
118
|
+
justify-content: space-between;
|
|
119
|
+
align-items: center;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* --- SEARCH DROPDOWN --- */
|
|
124
|
+
.search-container {
|
|
125
|
+
position: relative;
|
|
126
|
+
width: 100%;
|
|
127
|
+
max-width: 350px;
|
|
88
128
|
}
|
|
89
129
|
|
|
90
130
|
.search-dropdown {
|
|
91
131
|
position: absolute;
|
|
92
|
-
top: 100
|
|
132
|
+
top: calc(100% + 0.5rem);
|
|
93
133
|
left: 0;
|
|
94
134
|
right: 0;
|
|
95
|
-
|
|
96
|
-
background: #0d0d0d;
|
|
135
|
+
background: #000000;
|
|
97
136
|
border: 1px solid #333;
|
|
98
|
-
border-radius:
|
|
137
|
+
border-radius: 1rem;
|
|
99
138
|
box-shadow: 0 10px 30px rgba(0,0,0,0.5);
|
|
100
139
|
z-index: 100;
|
|
101
140
|
max-height: 300px;
|
|
102
141
|
overflow-y: auto;
|
|
103
142
|
display: none;
|
|
143
|
+
padding: 0.5rem;
|
|
104
144
|
}
|
|
105
145
|
.search-dropdown.active {
|
|
106
146
|
display: block;
|
|
147
|
+
animation: fadeIn 0.2s ease-out;
|
|
107
148
|
}
|
|
108
|
-
|
|
149
|
+
@keyframes fadeIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: translateY(0); } }
|
|
150
|
+
|
|
109
151
|
.search-item {
|
|
110
|
-
padding: 0.
|
|
152
|
+
padding: 0.6rem 0.8rem;
|
|
111
153
|
cursor: pointer;
|
|
112
|
-
border-
|
|
154
|
+
border-radius: 0.5rem;
|
|
113
155
|
display: flex;
|
|
114
156
|
flex-direction: column;
|
|
115
157
|
gap: 2px;
|
|
116
158
|
transition: background 0.2s;
|
|
159
|
+
border-bottom: 1px solid transparent;
|
|
117
160
|
}
|
|
118
|
-
.search-item:
|
|
119
|
-
.search-item
|
|
120
|
-
.search-item-
|
|
121
|
-
.search-item-sub { color: #707070; font-size: 0.8rem; }
|
|
161
|
+
.search-item:hover { background: #1a1a1a; color: #fff; }
|
|
162
|
+
.search-item-main { font-size: 0.95rem; color: #e5e7eb; }
|
|
163
|
+
.search-item-sub { color: #6b7280; font-size: 0.8rem; }
|
|
122
164
|
|
|
123
|
-
.geo-btn {
|
|
165
|
+
.geo-btn-absolute {
|
|
124
166
|
position: absolute;
|
|
125
167
|
right: 0.75rem;
|
|
126
168
|
top: 50%;
|
|
127
169
|
transform: translateY(-50%);
|
|
128
|
-
color: #
|
|
170
|
+
color: #6b7280;
|
|
129
171
|
cursor: pointer;
|
|
130
172
|
transition: color 0.2s;
|
|
131
173
|
background: none;
|
|
132
174
|
border: none;
|
|
133
175
|
}
|
|
134
|
-
.geo-btn:hover { color:
|
|
135
|
-
.geo-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
176
|
+
.geo-btn-absolute:hover { color: #4f46e5; }
|
|
136
177
|
|
|
137
178
|
/* --- WEATHER CARDS --- */
|
|
138
179
|
.weather-card {
|
|
139
180
|
background-color: #0d0d0d;
|
|
140
181
|
border: 1px solid #1a1a1a;
|
|
141
|
-
border-radius:
|
|
182
|
+
border-radius: 1.5rem;
|
|
142
183
|
padding: 1.5rem;
|
|
143
184
|
transition: border-color 0.2s;
|
|
144
185
|
}
|
|
@@ -149,42 +190,53 @@
|
|
|
149
190
|
/* Hero Section */
|
|
150
191
|
.current-temp {
|
|
151
192
|
font-family: 'Roboto', sans-serif;
|
|
152
|
-
font-size:
|
|
193
|
+
font-size: 4rem;
|
|
153
194
|
font-weight: 300;
|
|
154
195
|
color: #fff;
|
|
155
196
|
line-height: 1;
|
|
156
197
|
}
|
|
157
198
|
.current-icon {
|
|
158
|
-
font-size:
|
|
159
|
-
color: var(--
|
|
199
|
+
font-size: 3rem;
|
|
200
|
+
color: var(--color-indigo);
|
|
160
201
|
}
|
|
161
202
|
|
|
162
|
-
/* Hourly Scroll */
|
|
203
|
+
/* Hourly Scroll with Fading Edges */
|
|
204
|
+
.scroll-mask-container {
|
|
205
|
+
position: relative;
|
|
206
|
+
width: 100%;
|
|
207
|
+
/* CSS Mask for fading edges */
|
|
208
|
+
-webkit-mask-image: linear-gradient(to right, transparent 0%, black 5%, black 95%, transparent 100%);
|
|
209
|
+
mask-image: linear-gradient(to right, transparent 0%, black 5%, black 95%, transparent 100%);
|
|
210
|
+
}
|
|
211
|
+
|
|
163
212
|
.hourly-container {
|
|
164
213
|
display: flex;
|
|
165
214
|
overflow-x: auto;
|
|
166
|
-
gap:
|
|
167
|
-
padding
|
|
168
|
-
margin-top:
|
|
169
|
-
scrollbar-width:
|
|
170
|
-
|
|
215
|
+
gap: 0.75rem;
|
|
216
|
+
padding: 1rem 1.5rem; /* Extra padding for the mask to not cut off content immediately */
|
|
217
|
+
margin-top: 0.5rem;
|
|
218
|
+
scrollbar-width: none; /* Hide scrollbar Firefox */
|
|
219
|
+
-ms-overflow-style: none; /* Hide scrollbar IE/Edge */
|
|
171
220
|
}
|
|
221
|
+
.hourly-container::-webkit-scrollbar { display: none; } /* Hide scrollbar Chrome */
|
|
222
|
+
|
|
172
223
|
.hourly-item {
|
|
173
224
|
min-width: 80px;
|
|
174
225
|
background: #111;
|
|
175
226
|
border: 1px solid #222;
|
|
176
|
-
border-radius:
|
|
227
|
+
border-radius: 1rem;
|
|
177
228
|
padding: 1rem 0.5rem;
|
|
178
229
|
display: flex;
|
|
179
230
|
flex-direction: column;
|
|
180
231
|
align-items: center;
|
|
181
232
|
gap: 0.5rem;
|
|
182
|
-
cursor: pointer;
|
|
183
|
-
transition:
|
|
233
|
+
cursor: pointer;
|
|
234
|
+
transition: all 0.2s;
|
|
184
235
|
}
|
|
185
236
|
.hourly-item:hover {
|
|
186
237
|
background: #1a1a1a;
|
|
187
238
|
border-color: #333;
|
|
239
|
+
transform: translateY(-2px);
|
|
188
240
|
}
|
|
189
241
|
.hourly-time { font-size: 0.8rem; color: #707070; }
|
|
190
242
|
.hourly-temp { font-weight: 500; color: #fff; }
|
|
@@ -194,9 +246,12 @@
|
|
|
194
246
|
display: flex;
|
|
195
247
|
align-items: center;
|
|
196
248
|
justify-content: space-between;
|
|
197
|
-
padding: 1rem 0;
|
|
249
|
+
padding: 1rem 0.5rem;
|
|
198
250
|
border-bottom: 1px solid #1a1a1a;
|
|
251
|
+
transition: background 0.2s;
|
|
252
|
+
border-radius: 1rem;
|
|
199
253
|
}
|
|
254
|
+
.daily-row:hover { background-color: #111; }
|
|
200
255
|
.daily-row:last-child { border-bottom: none; }
|
|
201
256
|
.daily-day { width: 100px; font-weight: 500; color: #fff; }
|
|
202
257
|
.daily-icon { width: 40px; text-align: center; color: #9ca3af; }
|
|
@@ -217,21 +272,12 @@
|
|
|
217
272
|
gap: 1rem;
|
|
218
273
|
transition: opacity 0.5s;
|
|
219
274
|
}
|
|
220
|
-
.spinner {
|
|
221
|
-
width: 40px;
|
|
222
|
-
height: 40px;
|
|
223
|
-
border: 3px solid #333;
|
|
224
|
-
border-top-color: var(--accent-border);
|
|
225
|
-
border-radius: 50%;
|
|
226
|
-
animation: spin 1s linear infinite;
|
|
227
|
-
}
|
|
228
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
229
275
|
|
|
230
276
|
/* --- MODAL (HOURLY) --- */
|
|
231
277
|
#hourly-modal-overlay {
|
|
232
278
|
position: fixed;
|
|
233
279
|
inset: 0;
|
|
234
|
-
background: rgba(0,0,0,0.
|
|
280
|
+
background: rgba(0,0,0,0.8);
|
|
235
281
|
backdrop-filter: blur(5px);
|
|
236
282
|
z-index: 60;
|
|
237
283
|
display: flex;
|
|
@@ -245,7 +291,7 @@
|
|
|
245
291
|
.modal-content {
|
|
246
292
|
background: #0d0d0d;
|
|
247
293
|
border: 1px solid #333;
|
|
248
|
-
border-radius:
|
|
294
|
+
border-radius: 2rem;
|
|
249
295
|
padding: 2rem;
|
|
250
296
|
width: 90%;
|
|
251
297
|
max-width: 400px;
|
|
@@ -253,28 +299,22 @@
|
|
|
253
299
|
display: flex;
|
|
254
300
|
flex-direction: column;
|
|
255
301
|
gap: 1.5rem;
|
|
302
|
+
transform: scale(0.95);
|
|
303
|
+
transition: transform 0.2s;
|
|
256
304
|
}
|
|
257
|
-
.modal-
|
|
305
|
+
#hourly-modal-overlay.active .modal-content { transform: scale(1); }
|
|
306
|
+
|
|
307
|
+
.modal-header-text { font-size: 1.25rem; color: #fff; font-weight: 400; }
|
|
258
308
|
.detail-row { display: flex; justify-content: space-between; border-bottom: 1px solid #1a1a1a; padding-bottom: 0.5rem; }
|
|
259
309
|
.detail-label { color: #707070; font-size: 0.9rem; }
|
|
260
310
|
.detail-value { color: #fff; font-weight: 500; }
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
311
|
+
|
|
312
|
+
/* Provider Select styling fix */
|
|
313
|
+
select option {
|
|
314
|
+
background: #000;
|
|
264
315
|
color: #fff;
|
|
265
|
-
padding:
|
|
266
|
-
border-radius: 0.5rem;
|
|
267
|
-
cursor: pointer;
|
|
268
|
-
text-align: center;
|
|
269
|
-
margin-top: 0.5rem;
|
|
316
|
+
padding: 10px;
|
|
270
317
|
}
|
|
271
|
-
.close-modal-btn:hover { background: #1a1a1a; }
|
|
272
|
-
|
|
273
|
-
/* Scrollbar */
|
|
274
|
-
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
275
|
-
::-webkit-scrollbar-track { background: #040404; }
|
|
276
|
-
::-webkit-scrollbar-thumb { background: #333; border-radius: 4px; }
|
|
277
|
-
::-webkit-scrollbar-thumb:hover { background: #555; }
|
|
278
318
|
|
|
279
319
|
/* INJECTED STYLES */
|
|
280
320
|
:root { --bg-page: #040404; --bg-container: #000000; --border-color: #333; --text-primary: #c0c0c0; --text-light-grey: #d1d5db; --accent-color: #6366f1; --navbar-bg: #000000; --navbar-border: #1f2937; --tab-text: #9ca3af; --tab-hover-text: #ffffff; --tab-active-text: #4f46e5; --tab-active-bg: rgba(79, 70, 229, 0.1); --tab-active-border: #4f46e5; }
|
|
@@ -320,15 +360,13 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
320
360
|
</style>
|
|
321
361
|
|
|
322
362
|
</head>
|
|
323
|
-
<body class="min-h-screen
|
|
363
|
+
<body class="min-h-screen">
|
|
324
364
|
|
|
325
|
-
<!-- LOADING OVERLAY -->
|
|
326
365
|
<div id="loading-overlay">
|
|
327
|
-
<
|
|
366
|
+
<i class="fa-solid fa-spinner fa-spin fa-2x text-white mb-4"></i>
|
|
328
367
|
<div id="loading-text" class="text-sm tracking-widest uppercase text-gray-500">Locating...</div>
|
|
329
368
|
</div>
|
|
330
369
|
|
|
331
|
-
<!-- HOURLY DETAIL MODAL -->
|
|
332
370
|
<div id="hourly-modal-overlay">
|
|
333
371
|
<div class="modal-content">
|
|
334
372
|
<div class="flex items-center gap-4">
|
|
@@ -338,54 +376,46 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
338
376
|
<div id="modal-desc" class="text-gray-400 text-sm"></div>
|
|
339
377
|
</div>
|
|
340
378
|
</div>
|
|
341
|
-
<div id="modal-details-list" class="flex flex-col gap-3">
|
|
342
|
-
|
|
343
|
-
</
|
|
344
|
-
<button class="close-modal-btn" onclick="closeHourlyModal()">Close</button>
|
|
379
|
+
<div id="modal-details-list" class="flex flex-col gap-3 pt-2">
|
|
380
|
+
</div>
|
|
381
|
+
<button class="btn-toolbar-style w-full mt-4" onclick="closeHourlyModal()">Close</button>
|
|
345
382
|
</div>
|
|
346
383
|
</div>
|
|
347
384
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
<div class="w-full md:w-auto">
|
|
385
|
+
<header>
|
|
386
|
+
<div>
|
|
351
387
|
<h1 class="text-2xl font-bold text-white tracking-wide">4SP WEATHER</h1>
|
|
352
388
|
<div id="location-display" class="text-xs text-[#707070] flex items-center gap-2 mt-1">
|
|
353
|
-
|
|
389
|
+
<span id="loc-name">Detecting...</span>
|
|
354
390
|
</div>
|
|
355
391
|
</div>
|
|
356
392
|
|
|
357
|
-
<div class="flex items-center gap-4 w-full md:w-auto
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
<
|
|
361
|
-
<button id="btn-geo" class="geo-btn" title="Use Current Location">
|
|
393
|
+
<div class="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto">
|
|
394
|
+
<div class="search-container">
|
|
395
|
+
<input type="text" id="loc-search-input" placeholder="Change Location..." class="input-text-style pr-10">
|
|
396
|
+
<button id="btn-geo" class="geo-btn-absolute" title="Use Current Location">
|
|
362
397
|
<i class="fa-solid fa-location-crosshairs"></i>
|
|
363
398
|
</button>
|
|
364
399
|
|
|
365
|
-
<!-- Results Dropdown -->
|
|
366
400
|
<div id="search-results" class="search-dropdown">
|
|
367
|
-
|
|
368
|
-
</div>
|
|
401
|
+
</div>
|
|
369
402
|
</div>
|
|
370
403
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
<select id="provider-select" class="btn-toolbar appearance-none pr-8 outline-none focus:border-indigo-500">
|
|
404
|
+
<div class="relative w-full md:w-auto">
|
|
405
|
+
<select id="provider-select" class="btn-toolbar-style w-full appearance-none pr-8 outline-none">
|
|
374
406
|
<option value="nws">🇺🇸 NWS (USA)</option>
|
|
375
407
|
<option value="open-meteo">🌍 Open-Meteo</option>
|
|
376
408
|
</select>
|
|
377
|
-
<i class="fa-solid fa-chevron-down absolute right-3 top-1/2 -translate-y-1/2 text-xs pointer-events-none text-gray-
|
|
409
|
+
<i class="fa-solid fa-chevron-down absolute right-3 top-1/2 -translate-y-1/2 text-xs pointer-events-none text-gray-400"></i>
|
|
378
410
|
</div>
|
|
379
411
|
</div>
|
|
380
412
|
</header>
|
|
381
413
|
|
|
382
|
-
<!-- MAIN CONTENT -->
|
|
383
414
|
<main id="main-content" class="flex-1 p-4 md:p-8 max-w-5xl mx-auto w-full flex flex-col gap-6 opacity-0 transition-opacity duration-500">
|
|
384
415
|
|
|
385
|
-
<!-- CURRENT CONDITIONS HERO -->
|
|
386
416
|
<div class="weather-card flex flex-col md:flex-row items-center justify-between gap-8">
|
|
387
|
-
<div class="flex flex-col items-center md:items-start">
|
|
388
|
-
<div class="text-
|
|
417
|
+
<div class="flex flex-col items-center md:items-start text-center md:text-left">
|
|
418
|
+
<div class="text-xs text-[#4f46e5] uppercase tracking-widest font-bold mb-2">Current Conditions</div>
|
|
389
419
|
<div class="flex items-center gap-6">
|
|
390
420
|
<div id="curr-icon" class="current-icon"><i class="fa-solid fa-spinner fa-spin"></i></div>
|
|
391
421
|
<div>
|
|
@@ -395,40 +425,38 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
395
425
|
</div>
|
|
396
426
|
</div>
|
|
397
427
|
|
|
398
|
-
<div class="grid grid-cols-2 gap-x-12 gap-y-
|
|
399
|
-
<div>
|
|
400
|
-
<div class="text-
|
|
428
|
+
<div class="grid grid-cols-2 gap-x-12 gap-y-6 text-right w-full md:w-auto">
|
|
429
|
+
<div class="flex flex-col">
|
|
430
|
+
<div class="text-[10px] text-[#707070] uppercase tracking-wider mb-1">High</div>
|
|
401
431
|
<div id="curr-high" class="text-2xl text-white font-medium">--°</div>
|
|
402
432
|
</div>
|
|
403
|
-
<div>
|
|
404
|
-
<div class="text-
|
|
433
|
+
<div class="flex flex-col">
|
|
434
|
+
<div class="text-[10px] text-[#707070] uppercase tracking-wider mb-1">Low</div>
|
|
405
435
|
<div id="curr-low" class="text-2xl text-gray-400 font-medium">--°</div>
|
|
406
436
|
</div>
|
|
407
|
-
<div>
|
|
408
|
-
<div class="text-
|
|
437
|
+
<div class="flex flex-col">
|
|
438
|
+
<div class="text-[10px] text-[#707070] uppercase tracking-wider mb-1">Wind</div>
|
|
409
439
|
<div id="curr-wind" class="text-lg text-gray-300">--</div>
|
|
410
440
|
</div>
|
|
411
|
-
<div>
|
|
412
|
-
<div class="text-
|
|
441
|
+
<div class="flex flex-col">
|
|
442
|
+
<div class="text-[10px] text-[#707070] uppercase tracking-wider mb-1">Humidity</div>
|
|
413
443
|
<div id="curr-hum" class="text-lg text-gray-300">--%</div>
|
|
414
444
|
</div>
|
|
415
445
|
</div>
|
|
416
446
|
</div>
|
|
417
447
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
<div class="
|
|
421
|
-
|
|
422
|
-
|
|
448
|
+
<div class="weather-card overflow-hidden">
|
|
449
|
+
<div class="text-xs text-gray-500 uppercase tracking-widest mb-2 px-2">Hourly Forecast</div>
|
|
450
|
+
<div class="scroll-mask-container">
|
|
451
|
+
<div id="hourly-container" class="hourly-container">
|
|
452
|
+
</div>
|
|
423
453
|
</div>
|
|
424
454
|
</div>
|
|
425
455
|
|
|
426
|
-
<!-- DAILY FORECAST -->
|
|
427
456
|
<div class="weather-card">
|
|
428
|
-
<div class="text-
|
|
457
|
+
<div class="text-xs text-gray-500 uppercase tracking-widest mb-4">7-Day Forecast</div>
|
|
429
458
|
<div id="daily-container">
|
|
430
|
-
|
|
431
|
-
</div>
|
|
459
|
+
</div>
|
|
432
460
|
</div>
|
|
433
461
|
|
|
434
462
|
</main>
|
|
@@ -442,7 +470,7 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
442
470
|
region: null,
|
|
443
471
|
provider: 'nws',
|
|
444
472
|
data: null,
|
|
445
|
-
locationAllowed: false
|
|
473
|
+
locationAllowed: false
|
|
446
474
|
};
|
|
447
475
|
|
|
448
476
|
// --- DOM ELEMENTS ---
|
|
@@ -477,10 +505,9 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
477
505
|
const modalIconCont = document.getElementById('modal-icon-container');
|
|
478
506
|
const modalList = document.getElementById('modal-details-list');
|
|
479
507
|
|
|
480
|
-
// --- ICONS MAPPING
|
|
508
|
+
// --- ICONS MAPPING ---
|
|
481
509
|
function getIconFromWMO(code, isDay = 1) {
|
|
482
510
|
const day = isDay === 1;
|
|
483
|
-
// Mapped to FA 6 Free Icons
|
|
484
511
|
const map = {
|
|
485
512
|
0: day ? 'fa-sun' : 'fa-moon',
|
|
486
513
|
1: day ? 'fa-cloud-sun' : 'fa-cloud-moon',
|
|
@@ -498,7 +525,7 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
498
525
|
|
|
499
526
|
function getIconFromNWS(shortForecast, isDay) {
|
|
500
527
|
const lower = shortForecast.toLowerCase();
|
|
501
|
-
const prefix = 'fa-solid';
|
|
528
|
+
const prefix = 'fa-solid';
|
|
502
529
|
|
|
503
530
|
if (lower.includes('thunder')) return `${prefix} fa-cloud-bolt`;
|
|
504
531
|
if (lower.includes('snow') || lower.includes('flurries')) return `${prefix} fa-snowflake`;
|
|
@@ -571,7 +598,7 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
571
598
|
updateLocationDisplay();
|
|
572
599
|
} catch (e) {
|
|
573
600
|
console.warn("IP location fetch failed:", e);
|
|
574
|
-
// Fallback to defaults
|
|
601
|
+
// Fallback to defaults (NY)
|
|
575
602
|
STATE.lat = 40.7128;
|
|
576
603
|
STATE.lon = -74.0060;
|
|
577
604
|
STATE.city = "New York";
|
|
@@ -589,6 +616,10 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
589
616
|
searchResults.classList.remove('active');
|
|
590
617
|
return;
|
|
591
618
|
}
|
|
619
|
+
// Show loading state in dropdown
|
|
620
|
+
searchResults.innerHTML = '<div class="search-item text-gray-500"><i class="fa-solid fa-spinner fa-spin mr-2"></i> Searching...</div>';
|
|
621
|
+
searchResults.classList.add('active');
|
|
622
|
+
|
|
592
623
|
debounceTimer = setTimeout(() => fetchSearchResults(query), 500);
|
|
593
624
|
});
|
|
594
625
|
|
|
@@ -611,12 +642,10 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
611
642
|
}
|
|
612
643
|
|
|
613
644
|
try {
|
|
614
|
-
|
|
615
|
-
const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=20&addressdetails=1${viewbox}`;
|
|
645
|
+
const url = `https://nominatim.openstreetmap.org/search?format=json&q=${encodeURIComponent(query)}&limit=10&addressdetails=1${viewbox}`;
|
|
616
646
|
const res = await fetch(url);
|
|
617
647
|
const data = await res.json();
|
|
618
648
|
|
|
619
|
-
// Filter for only Places (cities, towns, villages) or Administrative boundaries
|
|
620
649
|
const filtered = data.filter(item => {
|
|
621
650
|
const type = item.type;
|
|
622
651
|
const category = item.class;
|
|
@@ -625,13 +654,13 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
625
654
|
|
|
626
655
|
if (!validClasses.includes(category)) return false;
|
|
627
656
|
if (category === 'boundary' && type !== 'administrative') return false;
|
|
628
|
-
|
|
629
657
|
return true;
|
|
630
658
|
});
|
|
631
659
|
|
|
632
|
-
renderSearchResults(filtered.slice(0, 5));
|
|
660
|
+
renderSearchResults(filtered.slice(0, 5));
|
|
633
661
|
} catch (e) {
|
|
634
662
|
console.error("Search failed", e);
|
|
663
|
+
searchResults.innerHTML = '<div class="search-item text-red-500">Error searching.</div>';
|
|
635
664
|
}
|
|
636
665
|
}
|
|
637
666
|
|
|
@@ -739,7 +768,7 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
739
768
|
}
|
|
740
769
|
}
|
|
741
770
|
|
|
742
|
-
// --- NWS
|
|
771
|
+
// --- NWS API ---
|
|
743
772
|
async function fetchNWS() {
|
|
744
773
|
loadingText.innerText = "Contacting National Weather Service...";
|
|
745
774
|
const pointsUrl = `https://api.weather.gov/points/${STATE.lat},${STATE.lon}`;
|
|
@@ -767,18 +796,15 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
767
796
|
|
|
768
797
|
function processNWSData(dailyRaw, hourlyRaw) {
|
|
769
798
|
const current = hourlyRaw.properties.periods[0];
|
|
770
|
-
|
|
771
799
|
const periodGroups = {};
|
|
772
800
|
|
|
773
801
|
dailyRaw.properties.periods.forEach(p => {
|
|
774
802
|
const date = p.startTime.split('T')[0];
|
|
775
803
|
if (!periodGroups[date]) {
|
|
776
|
-
periodGroups[date] = { temps: [], icons: [],
|
|
804
|
+
periodGroups[date] = { temps: [], icons: [], isDay: [] };
|
|
777
805
|
}
|
|
778
806
|
periodGroups[date].temps.push(p.temperature);
|
|
779
807
|
periodGroups[date].icons.push(p.shortForecast);
|
|
780
|
-
periodGroups[date].precips.push(p.probabilityOfPrecipitation.value || 0);
|
|
781
|
-
periodGroups[date].winds.push(parseInt(p.windSpeed) || 0);
|
|
782
808
|
periodGroups[date].isDay.push(p.isDaytime);
|
|
783
809
|
});
|
|
784
810
|
|
|
@@ -786,7 +812,6 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
786
812
|
const group = periodGroups[date];
|
|
787
813
|
const high = Math.max(...group.temps);
|
|
788
814
|
const low = Math.min(...group.temps);
|
|
789
|
-
|
|
790
815
|
let iconDesc = group.icons[0];
|
|
791
816
|
const dayIdx = group.isDay.indexOf(true);
|
|
792
817
|
if (dayIdx !== -1) iconDesc = group.icons[dayIdx];
|
|
@@ -807,8 +832,7 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
807
832
|
low: dailyList[0]?.low || current.temperature - 10,
|
|
808
833
|
wind: `${current.windSpeed} ${current.windDirection}`,
|
|
809
834
|
humidity: current.relativeHumidity.value,
|
|
810
|
-
iconClass: getIconFromNWS(current.shortForecast, current.isDaytime)
|
|
811
|
-
isNWS: true
|
|
835
|
+
iconClass: getIconFromNWS(current.shortForecast, current.isDaytime)
|
|
812
836
|
},
|
|
813
837
|
hourly: hourlyRaw.properties.periods.slice(0, 24).map(h => ({
|
|
814
838
|
time: new Date(h.startTime).getHours(),
|
|
@@ -826,7 +850,7 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
826
850
|
};
|
|
827
851
|
}
|
|
828
852
|
|
|
829
|
-
// --- OPEN-METEO ---
|
|
853
|
+
// --- OPEN-METEO API ---
|
|
830
854
|
async function fetchOpenMeteo() {
|
|
831
855
|
loadingText.innerText = "Contacting Open-Meteo...";
|
|
832
856
|
if (!STATE.city) updateLocationDisplay();
|
|
@@ -842,7 +866,6 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
842
866
|
const curr = data.current;
|
|
843
867
|
const daily = data.daily;
|
|
844
868
|
const hourly = data.hourly;
|
|
845
|
-
|
|
846
869
|
const now = new Date();
|
|
847
870
|
const currentHour = now.getHours();
|
|
848
871
|
|
|
@@ -850,13 +873,11 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
850
873
|
for(let i = currentHour; i < currentHour + 24; i++) {
|
|
851
874
|
if (hourly.time[i]) {
|
|
852
875
|
const d = new Date(hourly.time[i]);
|
|
853
|
-
const temp = Math.round(hourly.temperature_2m[i]);
|
|
854
876
|
const code = hourly.weather_code[i];
|
|
855
|
-
|
|
856
877
|
hourlyList.push({
|
|
857
878
|
time: d.getHours(),
|
|
858
879
|
fullTime: hourly.time[i],
|
|
859
|
-
temp:
|
|
880
|
+
temp: Math.round(hourly.temperature_2m[i]),
|
|
860
881
|
desc: getWMODescription(code),
|
|
861
882
|
iconClass: getIconFromWMO(code, hourly.is_day[i]),
|
|
862
883
|
details: {
|
|
@@ -888,8 +909,7 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
888
909
|
low: Math.round(daily.temperature_2m_min[0]),
|
|
889
910
|
wind: `${Math.round(curr.wind_speed_10m)} mph`,
|
|
890
911
|
humidity: curr.relative_humidity_2m,
|
|
891
|
-
iconClass: getIconFromWMO(curr.weather_code, curr.is_day)
|
|
892
|
-
isNWS: false
|
|
912
|
+
iconClass: getIconFromWMO(curr.weather_code, curr.is_day)
|
|
893
913
|
},
|
|
894
914
|
hourly: hourlyList,
|
|
895
915
|
daily: dailyList
|
|
@@ -936,8 +956,8 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
936
956
|
<div class="daily-day">${day.dateName}</div>
|
|
937
957
|
<div class="daily-icon text-xl"><i class="${day.iconClass}"></i></div>
|
|
938
958
|
<div class="daily-temps">
|
|
939
|
-
<span class="temp-high">${day.high}°</span>
|
|
940
|
-
<span class="temp-low">${day.low}°</span>
|
|
959
|
+
<span class="temp-high font-medium">${day.high}°</span>
|
|
960
|
+
<span class="temp-low text-sm">${day.low}°</span>
|
|
941
961
|
</div>
|
|
942
962
|
</div>
|
|
943
963
|
`).join('');
|
|
@@ -1037,4 +1057,4 @@ body:not(.no-animations) .btn-toolbar-style:active, body:not(.no-animations) .bt
|
|
|
1037
1057
|
</script>
|
|
1038
1058
|
|
|
1039
1059
|
</body>
|
|
1040
|
-
</html>
|
|
1060
|
+
</html>
|