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;
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