Skip to content

Commit baf7475

Browse files
Revamp time zone cards for sharing view (#31)
## Summary - Rework the tool into a set of cards including a shared-link preview, base timezone selector, sharing form, and preview table - Allow changing the detected base timezone with notes about how it was inferred and update shared conversions accordingly - Keep the comparison table empty until populated and improve shareable link presentation ------ [Codex Task](https://chatgpt.com/codex/tasks/task_e_6921afc7abd88325b9b166dc51ccfd75)
1 parent 307e400 commit baf7475

File tree

1 file changed

+145
-73
lines changed

1 file changed

+145
-73
lines changed

what-time-is-it-for-me.html

Lines changed: 145 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,38 @@
2222
padding: clamp(1.25rem, 3vw, 2rem);
2323
}
2424

25+
.card-header {
26+
display: flex;
27+
justify-content: space-between;
28+
align-items: baseline;
29+
gap: 0.5rem;
30+
flex-wrap: wrap;
31+
}
32+
33+
.shared-label {
34+
margin: 0 0 0.5rem;
35+
font-size: 1rem;
36+
color: var(--text-muted);
37+
}
38+
39+
.shared-target {
40+
margin: 0;
41+
font-size: clamp(1.4rem, 4vw, 2.4rem);
42+
font-weight: 700;
43+
}
44+
45+
.zone-note {
46+
color: var(--text-muted);
47+
margin-top: 0.35rem;
48+
font-size: 0.95rem;
49+
}
50+
51+
.inline-fields {
52+
display: grid;
53+
gap: 0.75rem;
54+
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
55+
}
56+
2557
.tool-actions {
2658
display: flex;
2759
flex-wrap: wrap;
@@ -105,6 +137,11 @@
105137
text-decoration: none;
106138
}
107139

140+
.comparison-empty {
141+
margin: 0;
142+
color: var(--text-muted);
143+
}
144+
108145
@media (max-width: 720px) {
109146
body {
110147
padding: 20px 16px 40px;
@@ -121,20 +158,39 @@ <h1>What time is it for me?</h1>
121158
</header>
122159

123160
<main>
124-
<section class="surface tool-card" aria-live="polite" aria-label="Inferred timezone">
125-
<p>Your inferred timezone: <span id="inferred-timezone-value">Detecting…</span></p>
161+
<section id="shared-moment-card" class="surface tool-card" aria-live="polite" hidden>
162+
<div class="card-header">
163+
<h2>Shared moment</h2>
164+
<span class="pill" id="shared-origin">From a shared link</span>
165+
</div>
166+
<p class="shared-label" id="shared-source"></p>
167+
<p class="shared-target" id="shared-target"></p>
168+
</section>
169+
170+
<section class="surface tool-card" aria-live="polite" aria-label="Base timezone">
171+
<div class="card-header">
172+
<h2>Base timezone</h2>
173+
<span class="zone-note" id="timezone-source-note"></span>
174+
</div>
175+
<p>Your detected time zone: <strong id="inferred-timezone-value">Detecting…</strong></p>
176+
<div class="form-group">
177+
<label for="base-timezone-select">Change base time zone</label>
178+
<select id="base-timezone-select" name="base-timezone"></select>
179+
</div>
126180
</section>
127181
<section class="surface tool-card" aria-labelledby="selection-heading">
128182
<h2 id="selection-heading">Pick a moment</h2>
129183
<form id="moment-form">
130-
<div class="form-group">
131-
<label for="datetime-input">Date and time</label>
132-
<input id="datetime-input" name="datetime" type="datetime-local" required>
133-
</div>
134-
<div class="form-group">
135-
<label for="timezone-select">Timezone</label>
136-
<select id="timezone-select" name="timezone" required>
137-
</select>
184+
<div class="inline-fields">
185+
<div class="form-group">
186+
<label for="datetime-input">Date and time</label>
187+
<input id="datetime-input" name="datetime" type="datetime-local" required>
188+
</div>
189+
<div class="form-group">
190+
<label for="timezone-select">Timezone</label>
191+
<select id="timezone-select" name="timezone" required>
192+
</select>
193+
</div>
138194
</div>
139195
<div class="tool-actions">
140196
<button type="button" id="copy-link-button">Copy link with this moment</button>
@@ -143,8 +199,21 @@ <h2 id="selection-heading">Pick a moment</h2>
143199
</form>
144200
</section>
145201

202+
<section class="surface tool-card" aria-labelledby="share-heading">
203+
<h2 id="share-heading">Shareable link</h2>
204+
<p>Use the button above to copy a link that includes this moment as URL parameters. Anyone who opens the
205+
link will see what that time looks like from their own timezone.</p>
206+
<div class="form-group">
207+
<label for="shareable-url">Preview of copied link</label>
208+
<input id="shareable-url" type="url" readonly value="">
209+
</div>
210+
</section>
211+
146212
<section class="surface tool-card" aria-live="polite" aria-labelledby="comparison-heading">
147-
<h2 id="comparison-heading">Comparison</h2>
213+
<div class="card-header">
214+
<h2 id="comparison-heading">Preview across time zones</h2>
215+
<p class="comparison-empty" id="comparison-hint">Add time zones to see how this moment appears elsewhere.</p>
216+
</div>
148217
<p id="comparison-output" class="lead">Choose a date, time, and timezone to see how it translates to your
149218
current timezone.</p>
150219
<div id="comparison-table" class="comparison-table" hidden>
@@ -163,16 +232,6 @@ <h2 id="comparison-heading">Comparison</h2>
163232
</div>
164233
</div>
165234
</section>
166-
167-
<section class="surface tool-card" aria-labelledby="share-heading">
168-
<h2 id="share-heading">Shareable link</h2>
169-
<p>Use the button above to copy a link that includes this moment as URL parameters. Anyone who opens the
170-
link will see what that time looks like from their own timezone.</p>
171-
<div class="form-group">
172-
<label for="shareable-url">Preview of copied link</label>
173-
<input id="shareable-url" type="url" readonly value="">
174-
</div>
175-
</section>
176235
</main>
177236

178237
<script src="https://cdn.jsdelivr.net/npm/[email protected]/build/global/luxon.min.js"></script>
@@ -190,21 +249,29 @@ <h2 id="share-heading">Shareable link</h2>
190249
const copyStatus = document.getElementById('copy-status');
191250
const shareableUrlInput = document.getElementById('shareable-url');
192251
const inferredTimezoneValue = document.getElementById('inferred-timezone-value');
252+
const baseTimezoneSelect = document.getElementById('base-timezone-select');
253+
const timezoneSourceNote = document.getElementById('timezone-source-note');
254+
const sharedMomentCard = document.getElementById('shared-moment-card');
255+
const sharedSource = document.getElementById('shared-source');
256+
const sharedTarget = document.getElementById('shared-target');
257+
const sharedOrigin = document.getElementById('shared-origin');
258+
const comparisonHint = document.getElementById('comparison-hint');
193259

194260
const resolvedZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
195261
const fallbackZone = resolvedZone || DateTime.local().zoneName || 'UTC';
196-
let localZone = fallbackZone;
197-
let localRowEntry = null;
262+
let baseZone = fallbackZone;
198263
let timezoneManuallySet = false;
199264
const comparisonRows = [];
265+
let sharedMoment = null;
200266

201267
function ensureOptionForZone(zone) {
202268
const exists = Array.from(timezoneSelect.options).some(option => option.value === zone);
203269
if (!exists && zone) {
204270
const option = document.createElement('option');
205271
option.value = zone;
206272
option.textContent = zone;
207-
timezoneSelect.append(option);
273+
timezoneSelect.append(option.cloneNode(true));
274+
baseTimezoneSelect.append(option);
208275
}
209276
}
210277

@@ -230,10 +297,11 @@ <h2 id="share-heading">Shareable link</h2>
230297
const option = document.createElement('option');
231298
option.value = zone;
232299
option.textContent = zone;
233-
timezoneSelect.append(option);
300+
timezoneSelect.append(option.cloneNode(true));
301+
baseTimezoneSelect.append(option);
234302
}
235303

236-
ensureOptionForZone(localZone);
304+
ensureOptionForZone(baseZone);
237305
}
238306

239307
function createTimezoneSelectElement() {
@@ -247,35 +315,6 @@ <h2 id="share-heading">Shareable link</h2>
247315
inferredTimezoneValue.textContent = zone || 'Unavailable';
248316
}
249317

250-
function addLocalComparisonRow() {
251-
const row = document.createElement('tr');
252-
row.className = 'comparison-row comparison-row-fixed';
253-
row.dataset.rowType = 'local';
254-
255-
const labelCell = document.createElement('th');
256-
labelCell.scope = 'row';
257-
labelCell.innerHTML = `<div class="comparison-label">Your timezone</div><div class="comparison-zone">${localZone}</div>`;
258-
259-
const timeCell = document.createElement('td');
260-
timeCell.className = 'comparison-time';
261-
262-
const actionCell = document.createElement('td');
263-
actionCell.className = 'comparison-actions-cell';
264-
265-
row.append(labelCell, timeCell, actionCell);
266-
comparisonRowsContainer.append(row);
267-
268-
localRowEntry = {
269-
getZone: () => localZone,
270-
timeCell,
271-
updateLabel(zone) {
272-
labelCell.innerHTML = `<div class="comparison-label">Your timezone</div><div class="comparison-zone">${zone}</div>`;
273-
}
274-
};
275-
276-
comparisonRows.push(localRowEntry);
277-
}
278-
279318
function addCustomComparisonRow(initialZone) {
280319
ensureOptionForZone(initialZone);
281320
const row = document.createElement('tr');
@@ -330,28 +369,32 @@ <h2 id="share-heading">Shareable link</h2>
330369
return row;
331370
}
332371

333-
function updateLocalZone(zone, { updateSelect = true, updateComparison = true } = {}) {
372+
function updateBaseZone(zone, note = '', { updateSelect = true, updateComparison: shouldUpdateComparison = true } = {}) {
334373
if (!zone) {
335374
updateInferredTimezoneDisplay('Unavailable');
336375
return;
337376
}
338377

339-
const previousZone = localZone;
340-
const zoneChanged = zone !== localZone;
341-
localZone = zone;
342-
343-
if (localRowEntry) {
344-
localRowEntry.updateLabel(zone);
345-
}
346-
378+
const previousZone = baseZone;
379+
const zoneChanged = zone !== baseZone;
380+
baseZone = zone;
347381
updateInferredTimezoneDisplay(zone);
382+
timezoneSourceNote.textContent = note || '';
348383
ensureOptionForZone(zone);
349384

385+
if (updateSelect) {
386+
baseTimezoneSelect.value = zone;
387+
}
388+
350389
if (updateSelect && !timezoneManuallySet && (timezoneSelect.value === previousZone || !timezoneSelect.value)) {
351390
timezoneSelect.value = zone;
352391
}
353392

354-
if (updateComparison && (zoneChanged || !comparisonTable.hidden)) {
393+
if (zoneChanged) {
394+
updateSharedMomentCard();
395+
}
396+
397+
if (shouldUpdateComparison && (zoneChanged || !comparisonTable.hidden)) {
355398
updateComparison();
356399
}
357400
}
@@ -401,7 +444,8 @@ <h2 id="share-heading">Shareable link</h2>
401444

402445
if (!datetimeValue || !selectedZone) {
403446
comparisonOutput.textContent = 'Choose a date, time, and timezone to see how it translates to your current timezone.';
404-
comparisonTable.hidden = true;
447+
comparisonTable.hidden = comparisonRows.length === 0;
448+
comparisonHint.hidden = comparisonRows.length > 0;
405449
shareableUrlInput.value = location.href;
406450
return;
407451
}
@@ -410,7 +454,8 @@ <h2 id="share-heading">Shareable link</h2>
410454

411455
if (!momentInSelectedZone.isValid) {
412456
comparisonOutput.textContent = 'The selected date or timezone could not be parsed. Please check your inputs.';
413-
comparisonTable.hidden = true;
457+
comparisonTable.hidden = comparisonRows.length === 0;
458+
comparisonHint.hidden = comparisonRows.length > 0;
414459
shareableUrlInput.value = location.href;
415460
return;
416461
}
@@ -419,7 +464,8 @@ <h2 id="share-heading">Shareable link</h2>
419464

420465
const sourceText = `${formatDateTime(momentInSelectedZone)} in ${momentInSelectedZone.zoneName} (${selectedOffset})`;
421466
comparisonOutput.textContent = `${sourceText}. Here's how that moment translates across the selected timezones:`;
422-
comparisonTable.hidden = false;
467+
comparisonTable.hidden = comparisonRows.length === 0;
468+
comparisonHint.hidden = comparisonRows.length > 0;
423469

424470
for (const entry of comparisonRows) {
425471
const zoneName = entry.getZone();
@@ -499,6 +545,14 @@ <h2 id="share-heading">Shareable link</h2>
499545
datetimeInput.value = combined;
500546
}
501547

548+
if (dateParam && timeParam && timezoneParam) {
549+
sharedMoment = DateTime.fromISO(`${dateParam}T${timeParam}`, { zone: timezoneParam });
550+
}
551+
552+
if (dateParam && timeParam) {
553+
sharedOrigin.textContent = 'Loaded from URL parameters';
554+
}
555+
502556
if (timezoneParam) {
503557
ensureOptionForZone(timezoneParam);
504558
timezoneSelect.value = timezoneParam;
@@ -508,6 +562,8 @@ <h2 id="share-heading">Shareable link</h2>
508562
if (datetimeInput.value && timezoneSelect.value) {
509563
updateComparison(true);
510564
}
565+
566+
updateSharedMomentCard();
511567
}
512568

513569
async function detectTimezoneFromIp() {
@@ -525,15 +581,28 @@ <h2 id="share-heading">Shareable link</h2>
525581
}
526582

527583
if (detectedZone) {
528-
updateLocalZone(detectedZone);
584+
updateBaseZone(detectedZone, 'Inferred from your IP address (worldtimeapi.org).');
529585
} else {
530-
updateLocalZone(fallbackZone, { updateSelect: false, updateComparison: false });
586+
updateBaseZone(fallbackZone, 'Using your browser-reported time zone.', { updateSelect: false, updateComparison: false });
587+
}
588+
}
589+
590+
function updateSharedMomentCard() {
591+
if (!sharedMoment || !sharedMoment.isValid) {
592+
sharedMomentCard.hidden = true;
593+
return;
531594
}
595+
596+
const baseZoneMoment = sharedMoment.setZone(baseZone);
597+
const sourceLine = `${sharedMoment.toFormat('HH:mm')} on the ${sharedMoment.toFormat('dd')} of ${sharedMoment.toFormat('LL')}, ${sharedMoment.toFormat('yyyy')} ${sharedMoment.zoneName} is`;
598+
sharedSource.textContent = sourceLine;
599+
sharedTarget.textContent = baseZoneMoment.isValid ? `${baseZoneMoment.toFormat('HH:mm')} on ${baseZoneMoment.toFormat('dd LLL yyyy')} (${baseZoneMoment.offsetNameShort})` : 'Unable to convert to your base time zone.';
600+
sharedMomentCard.hidden = false;
532601
}
533602

534603
populateTimezones();
535-
addLocalComparisonRow();
536-
timezoneSelect.value = localZone;
604+
timezoneSelect.value = baseZone;
605+
baseTimezoneSelect.value = baseZone;
537606
datetimeInput.value = DateTime.now().toISO({ suppressSeconds: true, suppressMilliseconds: true }).slice(0, 16);
538607
updateComparison(true);
539608

@@ -546,9 +615,12 @@ <h2 id="share-heading">Shareable link</h2>
546615
timezoneManuallySet = true;
547616
updateComparison();
548617
});
618+
baseTimezoneSelect.addEventListener('change', () => {
619+
updateBaseZone(baseTimezoneSelect.value, 'Manually selected base time zone.');
620+
});
549621
copyButton.addEventListener('click', copyLink);
550622
addComparisonRowButton.addEventListener('click', () => {
551-
const defaultZone = timezoneSelect.value || localZone;
623+
const defaultZone = timezoneSelect.value || baseZone;
552624
addCustomComparisonRow(defaultZone);
553625
updateComparison();
554626
});

0 commit comments

Comments
 (0)