2020 body { font-family : 'Inter' , sans-serif; background-color : var (--color-bg ); color : var (--color-text ); }
2121 .wrap { max-width : 1200px ; margin : 0 auto; padding : 2rem ; }
2222
23+ /* Timeline Row Styling */
2324 .timeline-row {
2425 background : var (--color-card );
2526 border : 1px solid var (--color-line );
2930 display : flex;
3031 align-items : center;
3132 gap : 20px ;
32- animation : slideIn 0.3s ease-out;
3333 }
3434
35- @keyframes slideIn { from { opacity : 0 ; transform : translateY (10px ); } to { opacity : 1 ; transform : translateY (0 ); } }
36-
37- .tz-info { width : 240px ; flex-shrink : 0 ; border-right : 1px solid var (--color-line ); padding-right : 15px ; }
35+ .tz-info { width : 240px ; flex-shrink : 0 ; border-right : 1px solid var (--color-line ); }
3836
3937 .grid-viewport { flex-grow : 1 ; overflow-x : auto; scrollbar-width : none; }
4038 .grid-viewport ::-webkit-scrollbar { display : none; }
4139
4240 .hour-grid { display : grid; grid-template-columns : repeat (24 , 42px ); gap : 4px ; }
4341
4442 .hour-cell {
45- height : 48px ;
46- background : rgba (255 , 255 , 255 , 0.03 );
43+ height : 48px ; background : rgba (255 , 255 , 255 , 0.03 );
4744 border : 1px solid rgba (255 , 255 , 255 , 0.08 );
48- border-radius : 6px ;
49- display : flex;
50- flex-direction : column;
51- align-items : center;
52- justify-content : center;
53- font-size : 0.7rem ;
54- cursor : pointer;
55- transition : all 0.2s ;
45+ border-radius : 6px ; display : flex; flex-direction : column;
46+ align-items : center; justify-content : center; font-size : 0.7rem ;
47+ cursor : pointer; transition : all 0.2s ;
5648 }
5749
58- .hour-cell : hover { border-color : var (--color-accent ); background : rgba (255 , 195 , 0 , 0.1 ); }
59- .hour-cell .active {
60- background : var (--color-accent ) !important ;
61- color : var (--color-bg ) !important ;
62- font-weight : 800 ;
63- transform : scale (1.05 );
64- z-index : 10 ;
65- }
50+ .hour-cell .active { background : var (--color-accent ) !important ; color : var (--color-bg ) !important ; font-weight : 800 ; transform : scale (1.05 ); z-index : 10 ; }
6651 .hour-cell .work { border-bottom : 3px solid # 4ade80 ; }
6752 .hour-cell .night { opacity : 0.4 ; background : rgba (0 , 0 , 0 , 0.2 ); }
6853
69- input # tz-search {
54+ /* Custom Dropdown/Search */
55+ .search-container { position : relative; width : 350px ; }
56+ # tz-input {
7057 background : # 0C1524 ; color : white; border : 1px solid var (--color-line );
71- padding : 12px 18px ; border-radius : 10px ; width : 350px ; outline : none;
72- box-shadow : 0 4px 15px rgba (0 , 0 , 0 , 0.2 );
58+ padding : 12px 18px ; border-radius : 10px ; width : 100% ; outline : none;
7359 }
74- input # tz-search : focus { border-color : var (--color-accent ); }
75-
76- .btn-add {
77- background : var (--color-accent ); color : var (--color-bg );
78- font-weight : 800 ; padding : 12px 24px ; border-radius : 10px ;
79- text-transform : uppercase; font-size : 0.75rem ; letter-spacing : 1px ;
60+ # tz-list {
61+ position : absolute; top : 110% ; left : 0 ; width : 100% ; max-height : 300px ;
62+ background : # 1e293b ; border : 1px solid var (--color-line ); border-radius : 10px ;
63+ overflow-y : auto; z-index : 100 ; display : none; box-shadow : 0 10px 25px rgba (0 , 0 , 0 , 0.5 );
8064 }
65+ .tz-option { padding : 10px 15px ; cursor : pointer; font-size : 0.85rem ; border-bottom : 1px solid rgba (255 , 255 , 255 , 0.05 ); }
66+ .tz-option : hover { background : var (--color-accent ); color : var (--color-bg ); font-weight : bold; }
8167 </ style >
8268</ head >
8369< body >
8874 < h1 class ="text-3xl font-black text-white italic "> TIME< span class ="text-[--color-accent] not-italic "> SYNC</ span > </ h1 >
8975 < p class ="text-[10px] text-[--color-muted] font-bold tracking-[0.2em] mt-1 uppercase "> Principal Engineering Toolkit</ p >
9076 </ div >
91- < div class ="flex gap-4 ">
92- < a href ="../stats " class ="text-[--color-muted] hover:text-[--color-accent] flex items-center gap-2 text-xs font-bold uppercase ">
93- < i class ="fa-solid fa-chart-simple "> </ i > Stats
94- </ a >
95- < a href ="../ " class ="bg-[--color-card] w-10 h-10 flex items-center justify-center rounded-xl border border-[--color-line] hover:border-[--color-accent] transition ">
96- < i class ="fa-solid fa-house-user "> </ i >
97- </ a >
98- </ div >
77+ < a href ="../ " class ="bg-[--color-card] w-10 h-10 flex items-center justify-center rounded-xl border border-[--color-line] hover:border-[--color-accent] transition ">
78+ < i class ="fa-solid fa-house-user "> </ i >
79+ </ a >
9980 </ header >
10081
10182 < div class ="bg-[--color-card] p-6 rounded-2xl mb-8 border border-[--color-line] flex flex-wrap gap-6 items-center ">
102- < div class ="flex flex-col gap-2 ">
103- < span class ="text-[10px] font-black text-[--color-muted] uppercase tracking-widest ml-1 "> Global Database Search</ span >
104- < div class ="flex gap-3 ">
105- < input type ="text " id ="tz-search " list ="all-timezones " placeholder ="Type city or country (e.g. London, UTC, IST)... ">
106- < datalist id ="all-timezones "> </ datalist >
107- < button onclick ="addZone() " class ="btn-add hover:brightness-110 active:scale-95 transition "> Add Zone</ button >
108- </ div >
83+ < div class ="search-container ">
84+ < span class ="text-[10px] font-black text-[--color-muted] uppercase tracking-widest ml-1 mb-2 block "> Search City / Region</ span >
85+ < input type ="text " id ="tz-input " placeholder ="Start typing (e.g. New York, Tokyo...) " onfocus ="showList() " oninput ="filterList() ">
86+ < div id ="tz-list "> </ div >
10987 </ div >
11088
11189 < div class ="ml-auto flex items-center gap-8 ">
11290 < div class ="text-right border-r border-[--color-line] pr-8 ">
11391 < p class ="text-[10px] uppercase text-[--color-muted] font-bold mb-1 "> Internal Reference</ p >
11492 < p id ="utc-clock " class ="font-mono text-xl text-white font-black "> 00:00 < span class ="text-[--color-accent] text-xs "> UTC</ span > </ p >
11593 </ div >
116- < button onclick ="resetToNow() " class ="text-[10px] font-black text-[--color-accent] border-2 border-[--color-accent] px-4 py-2 rounded-lg hover:bg-[--color-accent] hover:text-[--color-bg] transition-all ">
117- SYNC TO NOW
118- </ button >
94+ < button onclick ="resetToNow() " class ="text-[10px] font-black text-[--color-accent] border-2 border-[--color-accent] px-4 py-2 rounded-lg hover:bg-[--color-accent] hover:text-[--color-bg] transition-all uppercase "> Sync to Now</ button >
11995 </ div >
12096 </ div >
12197
122- < div id ="timeline-container " class ="space-y-3 ">
123- </ div >
124-
125- < div class ="mt-8 p-4 bg-blue-900/10 border border-blue-900/30 rounded-xl flex items-center gap-4 ">
126- < i class ="fa-solid fa-circle-info text-blue-400 "> </ i >
127- < p class ="text-xs text-blue-200/70 "> < b > Pro-Tip:</ b > Click any hour cell to align the timeline. Green underlines indicate 9 AM - 5 PM business hours for that specific region.</ p >
128- </ div >
98+ < div id ="timeline-container " class ="space-y-3 "> </ div >
12999</ div >
130100
131101< script >
132- // Initial state: Only show the user's current local timezone
133102 let activeUtcIndex = new Date ( ) . getUTCHours ( ) ;
134103 const localTZ = Intl . DateTimeFormat ( ) . resolvedOptions ( ) . timeZone ;
135104 let selectedZones = [ localTZ ] ;
136-
137- // Populate searchable IANA database
138105 const allZones = Intl . supportedValuesOf ( 'timeZone' ) ;
139- const datalist = document . getElementById ( 'all-timezones' ) ;
140- allZones . forEach ( zone => {
141- let opt = document . createElement ( 'option' ) ;
142- opt . value = zone ;
143- datalist . appendChild ( opt ) ;
106+
107+ // Setup Custom Search List
108+ const input = document . getElementById ( 'tz-input' ) ;
109+ const list = document . getElementById ( 'tz-list' ) ;
110+
111+ function showList ( ) { list . style . display = 'block' ; filterList ( ) ; }
112+
113+ // Hide list when clicking outside
114+ document . addEventListener ( 'click' , ( e ) => {
115+ if ( ! e . target . closest ( '.search-container' ) ) list . style . display = 'none' ;
144116 } ) ;
145117
146- function updateUTC ( ) {
147- const now = new Date ( ) ;
148- document . getElementById ( 'utc-clock' ) . innerHTML =
149- `${ now . getUTCHours ( ) . toString ( ) . padStart ( 2 , '0' ) } :${ now . getUTCMinutes ( ) . toString ( ) . padStart ( 2 , '0' ) } <span class="text-[--color-accent] text-xs">UTC</span>` ;
118+ function filterList ( ) {
119+ const filter = input . value . toLowerCase ( ) ;
120+ list . innerHTML = '' ;
121+ const filtered = allZones . filter ( z => z . toLowerCase ( ) . includes ( filter ) ) ;
122+
123+ filtered . forEach ( zone => {
124+ const div = document . createElement ( 'div' ) ;
125+ div . className = 'tz-option' ;
126+ div . innerText = zone . replace ( / _ / g, ' ' ) ;
127+ div . onclick = ( ) => {
128+ if ( ! selectedZones . includes ( zone ) ) {
129+ selectedZones . push ( zone ) ;
130+ render ( ) ;
131+ }
132+ input . value = '' ;
133+ list . style . display = 'none' ;
134+ } ;
135+ list . appendChild ( div ) ;
136+ } ) ;
150137 }
151138
152- function addZone ( ) {
153- const input = document . getElementById ( 'tz-search' ) ;
154- const val = input . value ;
155- if ( allZones . includes ( val ) && ! selectedZones . includes ( val ) ) {
156- selectedZones . push ( val ) ;
157- render ( ) ;
158- input . value = "" ;
159- } else if ( val === "UTC" && ! selectedZones . includes ( "UTC" ) ) {
160- selectedZones . push ( "UTC" ) ;
161- render ( ) ;
162- input . value = "" ;
163- }
139+ function updateUTC ( ) {
140+ const now = new Date ( ) ;
141+ document . getElementById ( 'utc-clock' ) . innerHTML = `${ now . getUTCHours ( ) . toString ( ) . padStart ( 2 , '0' ) } :${ now . getUTCMinutes ( ) . toString ( ) . padStart ( 2 , '0' ) } <span class="text-[--color-accent] text-xs">UTC</span>` ;
164142 }
165143
166144 function removeZone ( index ) {
167- if ( selectedZones . length > 1 ) {
168- selectedZones . splice ( index , 1 ) ;
169- render ( ) ;
170- }
145+ if ( selectedZones . length > 1 ) { selectedZones . splice ( index , 1 ) ; render ( ) ; }
171146 }
172147
173- function resetToNow ( ) {
174- activeUtcIndex = new Date ( ) . getUTCHours ( ) ;
175- render ( ) ;
176- }
148+ function resetToNow ( ) { activeUtcIndex = new Date ( ) . getUTCHours ( ) ; render ( ) ; }
177149
178150 function render ( ) {
179151 const container = document . getElementById ( 'timeline-container' ) ;
@@ -182,45 +154,34 @@ <h1 class="text-3xl font-black text-white italic">TIME<span class="text-[--color
182154 selectedZones . forEach ( ( zone , idx ) => {
183155 const row = document . createElement ( 'div' ) ;
184156 row . className = 'timeline-row' ;
185-
186157 const now = new Date ( ) ;
187158 const timeInZone = now . toLocaleTimeString ( 'en-US' , { timeZone : zone , hour : '2-digit' , minute : '2-digit' , hour12 : true } ) ;
188159 const offsetName = new Intl . DateTimeFormat ( 'en-US' , { timeZone : zone , timeZoneName :'short' } ) . formatToParts ( now ) . find ( p => p . type === 'timeZoneName' ) . value ;
189160
190161 row . innerHTML = `
191162 <div class="tz-info">
192- <div class="flex justify-between items-center mb-1">
193- <span class="font-black text-white text-sm tracking-tight">${ zone . split ( '/' ) . pop ( ) . replace ( '_' , ' ' ) } </span>
163+ <div class="flex justify-between items-center mb-1 pr-4 ">
164+ <span class="font-black text-white text-sm tracking-tight">${ zone . split ( '/' ) . pop ( ) . replace ( / _ / g , ' ' ) } </span>
194165 <span class="text-[11px] font-mono text-[--color-accent] font-bold">${ timeInZone } </span>
195166 </div>
196- <div class="flex justify-between items-center">
167+ <div class="flex justify-between items-center pr-4 ">
197168 <span class="text-[9px] text-[--color-muted] font-bold uppercase tracking-tighter">${ offsetName } </span>
198- ${ idx !== 0 ? `<button onclick="removeZone(${ idx } )" class="text-[9px] font-black text-red-400/50 hover:text-red-400 uppercase tracking-widest ">Delete</button>` : `<span class="text-[9px] text-blue-400 font-black uppercase tracking-widest ">Local</span>` }
169+ ${ idx !== 0 ? `<button onclick="removeZone(${ idx } )" class="text-[9px] font-black text-red-400/50 hover:text-red-400 uppercase">Delete</button>` : `<span class="text-[9px] text-blue-400 font-black uppercase">Local</span>` }
199170 </div>
200171 </div>
201- <div class="grid-viewport">
202- <div class="hour-grid"></div>
203- </div>
172+ <div class="grid-viewport"><div class="hour-grid" id="grid-${ idx } "></div></div>
204173 ` ;
205174
206175 const grid = row . querySelector ( '.hour-grid' ) ;
207-
208- // Build 24-hour UTC-aligned spine
209176 for ( let i = 0 ; i < 24 ; i ++ ) {
210177 const cell = document . createElement ( 'div' ) ;
211- const d = new Date ( ) ;
212- d . setUTCHours ( i , 0 , 0 , 0 ) ;
213-
178+ const d = new Date ( ) ; d . setUTCHours ( i , 0 , 0 , 0 ) ;
214179 const localHour = parseInt ( d . toLocaleTimeString ( 'en-US' , { timeZone : zone , hour12 : false , hour : 'numeric' } ) ) ;
215180 const ampm = d . toLocaleTimeString ( 'en-US' , { timeZone : zone , hour : 'numeric' , hour12 : true } ) . split ( ' ' ) [ 1 ] ;
216-
217- const isWork = localHour >= 9 && localHour <= 17 ;
218- const isNight = localHour >= 22 || localHour <= 6 ;
219181 const isActive = i === activeUtcIndex ;
220182
221- cell . className = `hour-cell ${ isWork ? 'work' : '' } ${ isNight ? 'night' : '' } ${ isActive ? 'active' : '' } ` ;
183+ cell . className = `hour-cell ${ localHour >= 9 && localHour <= 17 ? 'work' : '' } ${ localHour >= 22 || localHour <= 6 ? 'night' : '' } ${ isActive ? 'active' : '' } ` ;
222184 cell . innerHTML = `<span class="text-xs font-bold">${ localHour % 12 || 12 } </span><span class="text-[7px] font-black uppercase opacity-60">${ ampm } </span>` ;
223-
224185 cell . onclick = ( ) => { activeUtcIndex = i ; render ( ) ; } ;
225186 grid . appendChild ( cell ) ;
226187 }
@@ -232,6 +193,5 @@ <h1 class="text-3xl font-black text-white italic">TIME<span class="text-[--color
232193 render ( ) ;
233194 updateUTC ( ) ;
234195</ script >
235-
236196</ body >
237197</ html >
0 commit comments