301 status code means that the requested resource has been permanently moved to a new URL. All future requests should use the new address.
The browser will automatically redirect the user to the new address, and search engines will update their indexes.
200 status code is a standard successful HTTP server response. It means that the client’s request (e.g., from a browser) was successfully processed, and the server is delivering the requested data.
The user receives content without errors, and the page or application functions properly. If Code 200 is accompanied by data, the browser or program processes and displays it to the user.
GET / HTTP/1.1 Host: on4.com Accept: */* User-Agent: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; [email protected])
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Time Zone Converter</title> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <!-- Include LZ-String for compressing/decompressing state --> <script src="https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.4.4/lz-string.min.js"></script> <style> /* Brutalist Minimal */ :root { --accent: #d00; /* strong red accent */ --bg: #fff; --text: #000; --grey: #ccc; } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: monospace; background: var(--bg); color: var(--text); padding: 1rem; } h1, h2, h3 { margin: 0.5rem 0; text-transform: uppercase; } /* Tabs */ .tab-nav { display: flex; gap: 1rem; justify-content: center; margin-bottom: 1rem; } .tab-nav button { background: #eee; color: var(--text); border: 2px solid #000; padding: 0.5rem 1rem; font-weight: bold; text-transform: uppercase; cursor: pointer; } .tab-nav button.active { background: var(--accent); color: #fff; border-color: var(--accent); } .tab-content { display: none; max-width: 900px; margin: 0 auto; } .tab-content.active { display: block; } /* Card layout */ .card { border: 2px solid var(--grey); padding: 1rem; margin-bottom: 1rem; } label { font-weight: bold; display: block; margin-bottom: 0.2rem; } input, select { border: 2px solid #000; font-family: inherit; padding: 0.3rem; width: 100%; } .row { display: flex; gap: 1rem; } .btn { border: 2px solid #000; background: #eee; padding: 0.4rem 0.8rem; font-weight: bold; text-transform: uppercase; cursor: pointer; } .btn.accent { background: var(--accent); color: #fff; border-color: var(--accent); } /* Dropdown styling */ .dropdown { position: absolute; border: 2px solid #000; background: #fff; max-height: 150px; overflow-y: auto; display: none; z-index: 999999; } .dropdown.show { display: block; } .dropdown-item { padding: 0.3rem 0.5rem; cursor: pointer; } .dropdown-item:hover { background: #f88; } /* Overlaps container */ .overlaps { display: none; border: 2px solid var(--accent); margin-top: 0.5rem; padding: 0.5rem; } /* Targets (Meeting Scheduler) */ .target-item { display: flex; justify-content: space-between; align-items: center; border: 2px solid #000; padding: 0.3rem; margin-top: 0.3rem; } .time-display, .time-diff { margin-right: 0.3rem; } .remove-btn { background: none; border: none; color: var(--accent); cursor: pointer; font-weight: bold; margin-left: 0.3rem; } /* Time Slider tab */ .clocks-container { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.5rem; width: 100%; } .clock-card { border: 2px solid #000; padding: 0.5rem; text-align: center; position: relative; } .digital-clock { background: #000; color: #0f0; padding: 0.3rem 0.6rem; display: inline-block; margin: 0.5rem 0; font-weight: bold; } .base-badge { color: var(--accent); font-size: 0.8rem; font-weight: bold; } .remove-clock { color: var(--accent); font-size: 0.8rem; cursor: pointer; font-weight: bold; margin-top: 0.3rem; } .make-base { color: var(--accent); font-size: 0.8rem; font-weight: bold; cursor: pointer; margin-top: 0.3rem; } /* Range slider */ input[type="range"] { -webkit-appearance: none; height: 6px; cursor: pointer; background: transparent; outline: none; } input[type="range"]::-webkit-slider-runnable-track { height: 6px; background: var(--accent); border: 2px solid #000; } input[type="range"]::-moz-range-track { height: 6px; background: var(--accent); border: 2px solid #000; } input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 16px; height: 16px; background: #fff; border: 2px solid #000; margin-top: -6px; } input[type="range"]::-moz-range-thumb { width: 16px; height: 16px; background: #fff; border: 2px solid #000; } /* Text link styling for small actions */ .top5-link { font-size: 0.85rem; font-weight: normal; color: var(--accent); text-decoration: underline; margin-left: 1rem; cursor: pointer; } </style> <script async defer src="https://app.visitortracking.com/assets/js/tracer.js"></script> <script> function init_tracer() { var tracer = new Tracer({ websiteId : "b5a0706a-c49f-4ec3-9935-30032808f78e", async : true, debug : false }); } </script> <!-- Pixel Code - https://ore.io/ --> <script defer src="https://ore.io/pixel/BPYqh0Lcoh39ApjK"></script> <!-- END Pixel Code --> </head> <body> <!-- Tabs --> <div class="tab-nav"> <button id="tabBtn1" class="active">Meeting Scheduler</button> <button id="tabBtn2">Time Slider</button> </div> <!-- TAB #1: Meeting Scheduler --> <div id="tab1" class="tab-content active"> <h1>Meeting Scheduler</h1> <p style="margin-bottom:1rem;"> Pick a <strong>Source City</strong> and set your desired date/time, then add multiple <strong>Target Cities</strong> to compare local times. You can also find <strong>8:00–18:00 overlaps</strong> that suit all. </p> <div class="card"> <div class="row"> <div style="position: relative; flex: 1;"> <label>Source City</label> <input type="text" id="sourceCity"> <div class="dropdown" id="srcDD"></div> </div> <button id="useLocBtn" class="btn accent" style="align-self: flex-end;">Current</button> </div> <div class="row" style="margin-top: 0.5rem;"> <div style="flex: 1;"> <label>Date</label> <input type="date" id="sourceDate"> </div> <div style="flex: 1;"> <label>Time (30min)</label> <select id="sourceTimeSelect"></select> </div> </div> </div> <div class="card"> <div style="position: relative;"> <label>Target City</label> <input type="text" id="targetCity"> <div class="dropdown" id="tgtDD"></div> </div> <div id="tgtList"></div> </div> <div class="row" style="gap: 0.5rem;"> <button id="icsBtn" class="btn">Local Cal</button> <button id="gCalBtn" class="btn">Google Cal</button> <button id="overlapsBtn" class="btn">Overlaps</button> <!-- Deep Link button for Meeting Scheduler --> <button id="deepLinkBtnMeeting" class="btn">Share Link</button> <span id="resetLinkMeeting" class="top5-link">Clear</span> </div> <div class="overlaps" id="overlapsPanel"> <h3>8:00–18:00 Overlaps</h3> <p>In <span id="sourceCityLabel">[source]</span> these hours also work for all targets:</p> <div id="overlapsRes"></div> </div> </div> <!-- TAB #2: Time Slider --> <div id="tab2" class="tab-content"> <h1>Time Slider</h1> <p style="margin-bottom:1rem;"> Choose a <strong>Base City</strong> to anchor the slider, then add more cities. Adjust the slider (in 30-min increments) to see each city's corresponding local time. </p> <div class="card"> <div class="row" style="align-items: center;"> <label>Base Time</label> <input type="range" id="slider" min="0" max="47" step="1" value="0" style="flex: 1;"> <div id="sliderLabel" style="width: 50px; text-align: center;">00:00</div> </div> </div> <div class="card"> <!-- Label with three text links: Add Top 5, Share Link, and Reset --> <div style="position: relative;"> <label> Add City <span id="top5Link" class="top5-link">(Add Top 5)</span> <span id="deepLinkTextSlider" class="top5-link">(Share Link)</span> <span id="resetLinkSlider" class="top5-link">Clear</span> </label> <input type="text" id="sliderCity"> <div class="dropdown" id="sliderDD"></div> </div> </div> <!-- Container for clocks --> <div class="clocks-container" id="clockList"></div> </div> <script> document.addEventListener('DOMContentLoaded', () => { // --- TAB SWITCHING --- const tabBtn1 = document.getElementById('tabBtn1'), tabBtn2 = document.getElementById('tabBtn2'), tab1 = document.getElementById('tab1'), tab2 = document.getElementById('tab2'); tabBtn1.addEventListener('click', () => { tabBtn1.classList.add('active'); tabBtn2.classList.remove('active'); tab1.classList.add('active'); tab2.classList.remove('active'); }); tabBtn2.addEventListener('click', () => { tabBtn2.classList.add('active'); tabBtn1.classList.remove('active'); tab2.classList.add('active'); tab1.classList.remove('active'); }); // --- GLOBAL tzData --- window.tzData = []; fetch('tz.csv') .then(r => { if (!r.ok) throw new Error('tz.csv missing'); return r.text(); }) .then(txt => { parseTZCSV(txt); return fetch('extraCities.json'); }) .then(r => { if (!r.ok) { console.warn('No extraCities'); return []; } return r.json(); }) .then(ex => { if (Array.isArray(ex)) mergeExtra(ex); initScheduler(); initTimeSlider(); applyDeepLink(); // restore state from URL parameters }) .catch(err => { console.error(err); initScheduler(); initTimeSlider(); applyDeepLink(); }); // --- PARSE CSV --- function parseTZCSV(csv) { const lines = csv.trim().split(/\r?\n/); lines.shift(); // remove header lines.forEach(l => { const [rc, co, tz, ab] = l.split(',').map(x => x?.replace(/^"|"$/g, '').trim()); if (!tz) return; const synonyms = []; const seg = rc.split('/'); let city = seg[seg.length - 1]?.replace(/_/g, ' ') || tz; let rcLow = rc.toLowerCase(); if (rcLow && !synonyms.includes(rcLow)) synonyms.push(rcLow); if (city && !synonyms.includes(city.toLowerCase())) synonyms.push(city.toLowerCase()); if (co && !synonyms.includes(co.toLowerCase())) synonyms.push(co.toLowerCase()); (ab || '').split('|').forEach(a => { const aa = a.trim().toLowerCase(); if (aa && !synonyms.includes(aa)) synonyms.push(aa); }); tzData.push({ tz, city, synonyms }); }); } // --- Merge extra cities --- function mergeExtra(eArr) { eArr.forEach(o => { let f = tzData.find(x => x.tz === o.tz); if (!f) { f = { tz: o.tz, city: o.city || o.tz, synonyms: [] }; tzData.push(f); } const cLo = (o.city || '').toLowerCase(); const coLo = (o.country || '').toLowerCase(); if (cLo && !f.synonyms.includes(cLo)) f.synonyms.push(cLo); if (coLo && !f.synonyms.includes(coLo)) f.synonyms.push(coLo); }); } // --- MEETING SCHEDULER --- function initScheduler() { const sCity = document.getElementById('sourceCity'), sDD = document.getElementById('srcDD'), locBtn = document.getElementById('useLocBtn'), sDate = document.getElementById('sourceDate'), sTime = document.getElementById('sourceTimeSelect'), tCity = document.getElementById('targetCity'), tDD = document.getElementById('tgtDD'), tList = document.getElementById('tgtList'), icsBtn = document.getElementById('icsBtn'), gCalBtn = document.getElementById('gCalBtn'), overlapsBtn = document.getElementById('overlapsBtn'), overlapsPanel = document.getElementById('overlapsPanel'), srcCityLabel = document.getElementById('sourceCityLabel'), resetLinkMeeting = document.getElementById('resetLinkMeeting'); let sourceTZ = null, targets = []; // Fill time options if empty if (!sTime.options.length) { for (let h = 0; h < 24; h++) { for (let m = 0; m < 60; m += 30) { const val = `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`; const opt = document.createElement('option'); opt.value = val; opt.textContent = val; sTime.appendChild(opt); } } } sCity.addEventListener('input', () => searchZone(sCity, sDD, (found) => { sourceTZ = found.tz; sCity.value = `${found.city} (${found.tz})`; updateAllTimes(); }) ); tCity.addEventListener('input', () => searchZone(tCity, tDD, (found) => { if (!targets.find(x => x.tz === found.tz)) { targets.push({ tz: found.tz, city: found.city }); addTargetItem(found); } tCity.value = ''; updateAllTimes(); }) ); document.addEventListener('click', e => { if (!sDD.contains(e.target) && e.target !== sCity) sDD.style.display = 'none'; if (!tDD.contains(e.target) && e.target !== tCity) tDD.style.display = 'none'; }); locBtn.addEventListener('click', () => { const sys = Intl.DateTimeFormat().resolvedOptions().timeZone; const f = tzData.find(x => x.tz === sys); if (f) { sourceTZ = sys; sCity.value = `${f.city} (${sys})`; } else { sourceTZ = sys; sCity.value = `(${sys})`; } updateAllTimes(); }); function addTargetItem(o) { const div = document.createElement('div'); div.className = 'target-item'; div.dataset.tz = o.tz; div.innerHTML = ` <div> <strong>${o.city}</strong><br><small>${o.tz}</small> </div> <div> <span class="time-display"></span> <span class="time-diff"></span> <button class="remove-btn">x</button> </div> `; tList.appendChild(div); calcTime(div); div.querySelector('.remove-btn').addEventListener('click', () => { targets = targets.filter(x => x.tz !== o.tz); div.remove(); }); } function updateAllTimes() { document.querySelectorAll('.target-item').forEach(div => calcTime(div)); } function calcTime(div) { if (!sourceTZ) { div.querySelector('.time-display').textContent = ''; return; } const tz = div.dataset.tz; const utc = getSourceUTC(); const str = fmtLocal(utc, tz); div.querySelector('.time-display').textContent = str; const diff = getOffset(utc, tz) - getOffset(utc, sourceTZ); if (diff === 0) { div.querySelector('.time-diff').textContent = 'same'; } else { const hrs = Math.abs(Math.floor(diff / 60)); div.querySelector('.time-diff').textContent = diff > 0 ? `${hrs}h ahead` : `${hrs}h behind`; } } function getSourceUTC() { const dV = sDate.value, tV = sTime.value; if (!dV || !tV) return new Date(); const local = new Date(`${dV}T${tV}`); const off = getOffset(local, sourceTZ); const oh = Math.floor(Math.abs(off)/60), om = Math.abs(off)%60, sign = off >= 0 ? '+' : '-'; return new Date(`${dV}T${tV}:00${sign}${String(oh).padStart(2, '0')}:${String(om).padStart(2, '0')}`); } icsBtn.addEventListener('click', () => { if (!sourceTZ) { alert('No source'); return; } const st = getSourceUTC(), en = new Date(st.getTime() + 3600000); const ics = buildICS(st, en, (sCity.value || 'Online')); downloadFile(ics, 'event.ics', 'text/calendar'); }); gCalBtn.addEventListener('click', () => { if (!sourceTZ) { alert('No source'); return; } const start = getSourceUTC(), end = new Date(start.getTime() + 3600000); const S = fmtGCal(start), E = fmtGCal(end), sum = 'My Event', loc = sCity.value || 'Online', details = 'Scheduled'; const url = `https://calendar.google.com/calendar/render?action=TEMPLATE&text=${encodeURIComponent(sum)}&dates=${S}/${E}&details=${encodeURIComponent(details)}&location=${encodeURIComponent(loc)}`; window.open(url, '_blank'); }); overlapsBtn.addEventListener('click', () => { if (!sourceTZ) { alert('No source'); return; } if (!targets.length) { alert('No target'); return; } srcCityLabel.textContent = parseCity(sCity.value); const dayVal = sDate.value; const out = []; for (let h = 0; h < 24; h++) { for (let m = 0; m < 60; m += 30) { const hhmm = `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`; const local = new Date(`${dayVal}T${hhmm}`); const so = getOffset(local, sourceTZ); const oh = Math.floor(Math.abs(so)/60), om = Math.abs(so)%60, sg = so >= 0 ? '+' : '-'; const candidateUTC = new Date(`${dayVal}T${hhmm}:00${sg}${String(oh).padStart(2, '0')}:${String(om).padStart(2, '0')}`); const sr = fmtHour(candidateUTC, sourceTZ); if (sr < 8 || sr >= 18) continue; let allOk = true; for (let t of targets) { const h2 = fmtHour(candidateUTC, t.tz); if (h2 < 8 || h2 >= 18) { allOk = false; break; } } if (allOk) out.push(hhmm); } } document.getElementById('overlapsRes').innerHTML = out.length ? out.join('<br>') : 'No overlap found.'; overlapsPanel.style.display = 'block'; }); // --- Meeting Scheduler: Share Link --- const deepLinkBtnMeeting = document.getElementById('deepLinkBtnMeeting'); if (deepLinkBtnMeeting) { deepLinkBtnMeeting.addEventListener('click', () => { const url = getMeetingDeepLink(); navigator.clipboard.writeText(url).then(() => { alert('Deep link copied to clipboard:\n' + url); }, () => { alert('Failed to copy deep link'); }); }); } function getMeetingDeepLink() { const state = { tab: 'meeting', source: sCity.value, date: sDate.value, time: sTime.value, targets: [] }; const targetItems = document.querySelectorAll('#tgtList .target-item'); targetItems.forEach(item => { if (item.dataset.tz) state.targets.push(item.dataset.tz); }); const json = JSON.stringify(state); const compressed = LZString.compressToEncodedURIComponent(json); return window.location.origin + window.location.pathname + '?s=' + compressed; } // --- Meeting Scheduler: Reset --- resetLinkMeeting.addEventListener('click', () => { // Clear all fields and target list sourceTZ = null; sCity.value = ''; sDate.value = ''; sTime.selectedIndex = 0; tList.innerHTML = ''; targets = []; }); // Expose addTargetItem so deep linking can use it window.addTargetItem = addTargetItem; } // --- TIME SLIDER --- function initTimeSlider() { const slider = document.getElementById('slider'), sliderLabel = document.getElementById('sliderLabel'), sCity = document.getElementById('sliderCity'), dd = document.getElementById('sliderDD'), clockContainer = document.getElementById('clockList'), top5Link = document.getElementById('top5Link'), deepLinkTextSlider = document.getElementById('deepLinkTextSlider'), resetLinkSlider = document.getElementById('resetLinkSlider'); let baseTZ = null, clockList = []; sliderLabel.textContent = val2HHMM(slider.value); slider.addEventListener('input', () => { sliderLabel.textContent = val2HHMM(slider.value); updateAllClocks(); }); // "Add Top 5" link const popularTZs = [ "America/New_York", "America/Los_Angeles", "Europe/London", "Asia/Tokyo", "Australia/Sydney" ]; top5Link.addEventListener('click', (e) => { e.preventDefault(); popularTZs.forEach(tzName => { const f = tzData.find(x => x.tz === tzName); if (f) addClock(f); }); }); // --- Time Slider: Share Link --- deepLinkTextSlider.addEventListener('click', (e) => { e.preventDefault(); const url = getSliderDeepLink(); navigator.clipboard.writeText(url).then(() => { alert('Deep link copied to clipboard:\n' + url); }, () => { alert('Failed to copy deep link'); }); }); // --- Time Slider: Reset --- resetLinkSlider.addEventListener('click', () => { // Reset slider and remove all clocks slider.value = 0; sliderLabel.textContent = val2HHMM(slider.value); clockContainer.innerHTML = ''; clockList = []; baseTZ = null; sCity.value = ''; }); // Custom city search for slider sCity.addEventListener('input', () => { const q = sCity.value.trim().toLowerCase(); dd.innerHTML = ''; if (!q) { dd.style.display = 'none'; return; } let f = tzData.filter(o => o.city.toLowerCase().includes(q) || o.synonyms.some(x => x.includes(q))); if (!f.length) { dd.style.display = 'none'; return; } dd.innerHTML = f.slice(0, 10).map(o => ` <div class="dropdown-item" data-tz="${o.tz}"><b>${o.city}</b><br><small>${o.tz}</small></div> `).join(''); dd.style.display = 'block'; dd.querySelectorAll('.dropdown-item').forEach(el => { el.addEventListener('click', () => { let z = tzData.find(xx => xx.tz === el.dataset.tz); if (z) addClock(z); dd.style.display = 'none'; sCity.value = ''; }); }); }); document.addEventListener('click', e => { if (!dd.contains(e.target) && e.target !== sCity) dd.style.display = 'none'; }); function addClock(o) { if (clockList.find(x => x.tz === o.tz)) return; const div = document.createElement('div'); div.className = 'clock-card'; div.dataset.tz = o.tz; div.innerHTML = ` <h3>${o.city}</h3> <div class="digital-clock">--:--</div> <div class="remove-clock">Remove</div> <span class="make-base">Make Base</span> <div class="base-badge" style="display:none;">(BASE)</div> `; clockContainer.appendChild(div); let cObj = { tz: o.tz, city: o.city, elm: div, isBase: false }; clockList.push(cObj); if (!baseTZ) { setBase(o.tz); } div.querySelector('.remove-clock').addEventListener('click', () => { clockList = clockList.filter(x => x.tz !== o.tz); div.remove(); if (o.tz === baseTZ) { if (clockList.length) setBase(clockList[0].tz); else baseTZ = null; } }); div.querySelector('.make-base').addEventListener('click', () => { setBase(o.tz); updateAllClocks(); }); updateClock(cObj); } function setBase(tz) { baseTZ = tz; clockList.forEach(x => x.isBase = (x.tz === tz)); updateBadges(); } function updateBadges() { clockList.forEach(x => { let bb = x.elm.querySelector('.base-badge'), mk = x.elm.querySelector('.make-base'); if (x.isBase) { bb.style.display = 'inline'; mk.style.display = 'none'; } else { bb.style.display = 'none'; mk.style.display = 'inline'; } }); } function updateAllClocks() { clockList.forEach(c => updateClock(c)); } function updateClock(c) { if (!baseTZ) return; let utc = getBaseUTC(); let local = toLocal(utc, c.tz); let hh = String(local.getHours()).padStart(2, '0'), mm = String(local.getMinutes()).padStart(2, '0'); let baseLoc = toLocal(utc, baseTZ); let ddiff = dayNum(local) - dayNum(baseLoc); let dayL = ddiff === 0 ? '' : ddiff > 0 ? ` (+${ddiff}d)` : ` (${ddiff}d)`; c.elm.querySelector('.digital-clock').textContent = `${hh}:${mm}${dayL}`; } function getBaseUTC() { if (!baseTZ) return new Date(); let val = +slider.value, tot = val * 30; let now = new Date(), y = now.getUTCFullYear(), m = now.getUTCMonth(), d = now.getUTCDate(); let localMid = new Date(Date.UTC(y, m, d, 0, 0, 0)), off = getOffset(localMid, baseTZ); localMid.setUTCMinutes(localMid.getUTCMinutes() + off + tot); return localMid; } function val2HHMM(v) { let tot = +v * 30, h = Math.floor(tot / 60), mm = tot % 60; return `${String(h).padStart(2, '0')}:${String(mm).padStart(2, '0')}`; } function toLocal(utc, tz) { return new Date(utc.getTime() + getOffset(utc, tz) * 60000); } function dayNum(d) { let x = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())); return Math.floor(x.getTime() / 86400000); } // --- Time Slider: Generate Short Deep Link --- function getSliderDeepLink() { const state = { tab: 'slider', slider: slider.value, base: null, clocks: [] }; // Determine the base timezone from clock cards: const clockCards = document.querySelectorAll('#clockList .clock-card'); clockCards.forEach(card => { if (card.querySelector('.base-badge') && card.querySelector('.base-badge').style.display !== 'none') { state.base = card.dataset.tz; } }); clockCards.forEach(card => { state.clocks.push(card.dataset.tz); }); const json = JSON.stringify(state); const compressed = LZString.compressToEncodedURIComponent(json); return window.location.origin + window.location.pathname + '?s=' + compressed; } // Expose addClock for deep linking window.addClock = addClock; } // --- APPLY DEEP LINK ON PAGE LOAD --- function applyDeepLink() { const params = new URLSearchParams(window.location.search); if (params.has('s')) { // Use compressed state if available const compressed = params.get('s'); const json = LZString.decompressFromEncodedURIComponent(compressed); if (json) { const state = JSON.parse(json); if (state.tab === 'meeting') { document.getElementById('tabBtn1').click(); if (state.source) { document.getElementById('sourceCity').value = state.source; document.getElementById('sourceCity').dispatchEvent(new Event('input')); } if (state.date) document.getElementById('sourceDate').value = state.date; if (state.time) document.getElementById('sourceTimeSelect').value = state.time; if (state.targets && Array.isArray(state.targets)) { state.targets.forEach(tz => { const found = tzData.find(x => x.tz === tz); if (found && window.addTargetItem) { window.addTargetItem(found); } }); } } else if (state.tab === 'slider') { document.getElementById('tabBtn2').click(); if (state.slider) { const slider = document.getElementById('slider'); slider.value = state.slider; slider.dispatchEvent(new Event('input')); } if (state.clocks && Array.isArray(state.clocks)) { state.clocks.forEach(tz => { const found = tzData.find(x => x.tz === tz); if (found && window.addClock) { window.addClock(found); } }); } if (state.base) { const clockCards = document.querySelectorAll('#clockList .clock-card'); clockCards.forEach(card => { if (card.dataset.tz === state.base) { card.querySelector('.make-base').click(); } }); } } } } else if (params.get('tab')) { // Fallback: use uncompressed query parameters if available const tab = params.get('tab'); if (tab === 'meeting') { document.getElementById('tabBtn1').click(); const source = params.get('source'), date = params.get('date'), time = params.get('time'); if (source) { document.getElementById('sourceCity').value = source; document.getElementById('sourceCity').dispatchEvent(new Event('input')); } if (date) document.getElementById('sourceDate').value = date; if (time) document.getElementById('sourceTimeSelect').value = time; const targets = params.get('targets'); if (targets) { const targetArr = targets.split(','); targetArr.forEach(tz => { const found = tzData.find(x => x.tz === tz); if (found && window.addTargetItem) { window.addTargetItem(found); } }); } } else if (tab === 'slider') { document.getElementById('tabBtn2').click(); const sliderVal = params.get('slider'); if (sliderVal) { const slider = document.getElementById('slider'); slider.value = sliderVal; slider.dispatchEvent(new Event('input')); } const clocks = params.get('clocks'); if (clocks) { const clocksArr = clocks.split(','); clocksArr.forEach(tz => { const found = tzData.find(x => x.tz === tz); if (found && window.addClock) { window.addClock(found); } }); } const base = params.get('base'); if (base) { const clockCards = document.querySelectorAll('#clockList .clock-card'); clockCards.forEach(card => { if (card.dataset.tz === base) { card.querySelector('.make-base').click(); } }); } } } } // --- HELPER FUNCTIONS --- function searchZone(inp, dd, cb) { const q = inp.value.trim().toLowerCase(); dd.innerHTML = ''; if (!q) { dd.style.display = 'none'; return; } const found = tzData.filter(o => o.city.toLowerCase().includes(q) || o.synonyms.some(s => s.includes(q))); if (!found.length) { dd.style.display = 'none'; return; } dd.innerHTML = found.slice(0, 10).map(x => ` <div class="dropdown-item" data-tz="${x.tz}"> <b>${x.city}</b><br><small>${x.tz}</small> </div> `).join(''); dd.style.display = 'block'; dd.querySelectorAll('.dropdown-item').forEach(el => { el.addEventListener('click', () => { const z = tzData.find(fz => fz.tz === el.dataset.tz); if (z) cb(z); dd.style.display = 'none'; }); }); } function getOffset(d, tz) { const f = new Intl.DateTimeFormat('en', { timeZone: tz, timeZoneName: 'shortOffset' }).formatToParts(d); const nm = f.find(x => x.type === 'timeZoneName')?.value || ''; const m = nm.match(/GMT([+-]\d+)(?::(\d+))?/); if (!m) return 0; const s = m[1].startsWith('-') ? -1 : 1, hrs = parseInt(m[1].replace(/\D/g, ''), 10), mn = m[2] ? parseInt(m[2], 10) : 0; return s * (hrs * 60 + mn); } function fmtLocal(u, tz) { const opt = { timeZone: tz, hour12: false, weekday: 'short', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }; return new Intl.DateTimeFormat('en-US', opt).format(u); } function fmtHour(u, tz) { const opt = { timeZone: tz, hour: '2-digit', hour12: false }; return parseInt(new Intl.DateTimeFormat('en', opt).format(u), 10); } function parseCity(str) { const i = str.indexOf(' ('); return i > 0 ? str.slice(0, i).trim() : str; } function buildICS(st, en, loc) { const f = d => { const y = d.getUTCFullYear(), mo = String(d.getUTCMonth() + 1).padStart(2, '0'), da = String(d.getUTCDate()).padStart(2, '0'), hh = String(d.getUTCHours()).padStart(2, '0'), mm = String(d.getUTCMinutes()).padStart(2, '0'), ss = String(d.getUTCSeconds()).padStart(2, '0'); return `${y}${mo}${da}T${hh}${mm}${ss}Z`; }; const s = f(st), e = f(en), ds = f(new Date()); return `BEGIN:VCALENDAR VERSION:2.0 PRODID:-//BrutalistTZ//EN BEGIN:VEVENT UID:${Date.now()} DTSTAMP:${ds} DTSTART:${s} DTEND:${e} SUMMARY:My Event LOCATION:${loc} END:VEVENT END:VCALENDAR`; } function downloadFile(content, name, type) { const b = new Blob([content], { type }), u = URL.createObjectURL(b); const a = document.createElement('a'); a.href = u; a.download = name; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(u); } function fmtGCal(d) { const y = d.getUTCFullYear(), mo = String(d.getUTCMonth() + 1).padStart(2, '0'), da = String(d.getUTCDate()).padStart(2, '0'), hh = String(d.getUTCHours()).padStart(2, '0'), mm = String(d.getUTCMinutes()).padStart(2, '0'), ss = String(d.getUTCSeconds()).padStart(2, '0'); return `${y}${mo}${da}T${hh}${mm}${ss}Z`; } }); </script> </body> </html>