summary history files

timezones/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Find the best time to meet across multiple timezones with an interactive comparison table and markdown export.">
<title>Timezone Meeting Planner</title>
<style>
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}
:root {
  --bg-color: #fafafa;
  --card-bg: white;
  --text-color: #333;
  --text-secondary: #666;
  --border-color: #ddd;
  --primary-color: #0066cc;
  --primary-hover: #0052a3;
  --secondary-color: #6c757d;
  --secondary-hover: #5a6268;
  --danger-color: #dc3545;
  --danger-hover: #c82333;
  --accent-bg: #f8f9fa;
  --hover-bg: #f0f0f0;
  --selected-bg: #e3f2fd;
  --shadow: 0 1px 3px rgba(0,0,0,0.1);
  --code-bg: #f4f4f4;
}
@media (prefers-color-scheme: dark) {
  :root {
    --bg-color: #1a1a1a;
    --card-bg: #2d2d2d;
    --text-color: #e0e0e0;
    --text-secondary: #aaa;
    --border-color: #404040;
    --primary-color: #4da3ff;
    --primary-hover: #6bb3ff;
    --secondary-color: #8a949e;
    --secondary-hover: #9aa5b0;
    --danger-color: #ff6b6b;
    --danger-hover: #ff8585;
    --accent-bg: #363636;
    --hover-bg: #404040;
    --selected-bg: #1e3a5f;
    --shadow: 0 1px 3px rgba(0,0,0,0.3);
    --code-bg: #1e1e1e;
  }
  table {
    color: var(--text-color);
  }
  th {
    color: var(--text-color);
  }
  .date-indicator {
    color: #777;
  }
}
body {
  font-family: system-ui, -apple-system, sans-serif;
  line-height: 1.5;
  color: var(--text-color);
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
  background: var(--bg-color);
  transition: background-color 0.2s;
}
h1 {
  font-size: 1.5rem;
  margin-bottom: 0.5rem;
  font-weight: 600;
}
.description {
  color: var(--text-secondary);
  font-size: 0.95rem;
  margin-bottom: 1.5rem;
}
.controls {
  background: var(--card-bg);
  padding: 20px;
  border-radius: 8px;
  box-shadow: var(--shadow);
  margin-bottom: 20px;
  border: 1px solid var(--border-color);
}
.timezone-row {
  display: flex;
  gap: 10px;
  margin-bottom: 10px;
  align-items: start;
}
.timezone-row input[type="text"] {
  padding: 8px 12px;
  border: 1px solid var(--border-color);
  border-radius: 4px;
  font-size: 0.9rem;
  background: var(--card-bg);
  color: var(--text-color);
}
.name-input {
  width: 140px;
}
.tz-container {
  flex: 1;
  position: relative;
  min-width: 200px;
}
.tz-input {
  width: 100%;
}
.tz-dropdown {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  max-height: 200px;
  overflow-y: auto;
  background: var(--card-bg);
  border: 1px solid var(--border-color);
  border-top: none;
  border-radius: 0 0 4px 4px;
  z-index: 100;
  display: none;
  box-shadow: var(--shadow);
}
.tz-dropdown.active {
  display: block;
}
.tz-option {
  padding: 8px 12px;
  cursor: pointer;
  font-size: 0.9rem;
  color: var(--text-color);
}
.tz-option:hover, .tz-option.selected {
  background: var(--hover-bg);
}
button {
  padding: 8px 16px;
  border: 1px solid var(--border-color);
  background: var(--card-bg);
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.9rem;
  transition: all 0.2s;
  color: var(--text-color);
}
button:hover {
  background: var(--hover-bg);
}
button.primary {
  background: var(--primary-color);
  color: #ffffff;
  border-color: var(--primary-color);
  font-weight: 500;
}
button.primary:hover {
  background: var(--primary-hover);
}
button.secondary {
  background: var(--secondary-color);
  color: #ffffff;
  border-color: var(--secondary-color);
  font-weight: 500;
}
button.secondary:hover {
  background: var(--secondary-hover);
}
button.danger {
  color: var(--danger-color);
  border-color: var(--danger-color);
}
button.danger:hover {
  background: var(--danger-color);
  color: white;
}
button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.actions {
  margin-top: 15px;
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}
.checkbox-label {
  display: flex;
  align-items: center;
  gap: 5px;
  font-size: 0.9rem;
  margin-left: auto;
  color: var(--text-color);
}
.error-message {
  color: var(--danger-color);
  font-size: 0.85rem;
  margin-bottom: 10px;
  padding: 8px;
  background: rgba(220, 53, 69, 0.1);
  border-radius: 4px;
  display: none;
}
.error-message.visible {
  display: block;
}
.loading {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--text-secondary);
  font-size: 0.9rem;
  margin: 10px 0;
}
.loading::after {
  content: '';
  width: 16px;
  height: 16px;
  border: 2px solid var(--border-color);
  border-top-color: var(--primary-color);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin {
  to { transform: rotate(360deg); }
}
.shortcut-hint {
  font-size: 0.75rem;
  opacity: 0.8;
  margin-left: 4px;
}
table {
  width: 100%;
  background: var(--card-bg);
  border-collapse: collapse;
  box-shadow: var(--shadow);
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--border-color);
  user-select: none;
}
th, td {
  padding: 12px;
  text-align: center;
  border-bottom: 1px solid var(--border-color);
}
th {
  background: var(--accent-bg);
  font-weight: 600;
  font-size: 0.9rem;
  position: sticky;
  top: 0;
}
td {
  font-size: 0.9rem;
  cursor: pointer;
}
tr:hover {
  background: var(--hover-bg);
}
tr.selected {
  background: var(--selected-bg);
}
tr.selected td {
  font-weight: 500;
}
.hour-cell {
  font-weight: 600;
  color: var(--text-secondary);
  width: 80px;
}
.date-indicator {
  font-size: 0.75rem;
  color: #999;
  margin-left: 4px;
}
.hidden {
  display: none;
}
.markdown-container {
  margin-top: 20px;
}
.markdown-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
  background: var(--accent-bg);
  padding: 10px 15px;
  border-radius: 4px 4px 0 0;
  border: 1px solid var(--border-color);
  border-bottom: none;
}
.markdown-view {
  background: var(--code-bg);
  border: 1px solid var(--border-color);
  border-radius: 0 0 4px 4px;
  padding: 15px;
  font-family: 'Courier New', Consolas, monospace;
  font-size: 0.85rem;
  white-space: pre;
  overflow-x: auto;
  line-height: 1.4;
  color: var(--text-color);
}
.url-display {
  margin-top: 10px;
  padding: 10px;
  background: var(--accent-bg);
  border: 1px solid var(--border-color);
  border-radius: 4px;
  font-family: 'Courier New', Consolas, monospace;
  font-size: 0.8rem;
  word-break: break-all;
  color: var(--text-color);
}
@media (max-width: 768px) {
  .timezone-row {
    flex-direction: column;
    align-items: stretch;
  }
  .name-input, .tz-container {
    width: 100%;
  }
  .actions {
    flex-direction: column;
    align-items: stretch;
  }
  .checkbox-label, .shortcut-hint {
    margin-left: 0;
    margin-top: 10px;
  }
  table {
    font-size: 0.85rem;
  }
  th, td {
    padding: 8px 4px;
  }
  .markdown-header {
    flex-direction: column;
    gap: 10px;
  }
  .tz-dropdown {
    max-height: 150px;
    font-size: 0.85rem;
  }
}
</style>
</head>
<body>
<h1>Timezone Meeting Planner</h1>
<p class="description">Find the best time to meet across multiple timezones with an interactive comparison table and markdown export.</p>

<div class="controls">
  <div id="errorContainer" class="error-message"></div>
  <div id="loadingIndicator" class="loading hidden">Loading timezones...</div>
  <div id="inputs"></div>
  <div class="actions">
    <button id="addBtn" onclick="addTimezone()">Add Timezone</button>
    <button class="primary" onclick="generateTable()">Generate Table<span class="shortcut-hint">Ctrl+Enter</span></button>
    <button class="secondary hidden" id="toggleViewBtn" onclick="toggleView()">Generate as Markdown</button>
    <button class="danger" onclick="clearAll()">Clear</button>
    <label class="checkbox-label">
      <input type="checkbox" id="includeUtc"> Include UTC
    </label>
  </div>
  <div class="actions" style="margin-top: 10px;">
    <button onclick="generateShareableUrl()">Generate Shareable URL</button>
    <div id="urlDisplay" class="url-display hidden"></div>
  </div>
</div>

<div id="output" class="hidden">
  <table id="timetable">
    <thead>
      <tr id="headerRow">
        <th>Hour</th>
      </tr>
    </thead>
    <tbody id="tableBody"></tbody>
  </table>
  <div id="markdownContainer" class="markdown-container hidden">
    <div class="markdown-header">
      <span>Markdown format</span>
      <button class="primary" onclick="copyToClipboard()">Copy to Clipboard</button>
    </div>
    <pre id="markdownView" class="markdown-view"></pre>
  </div>
</div>

<script>
let allZones = [];
let timezones = [];

try {
  allZones = Intl.supportedValuesOf('timeZone');
  timezones = ['UTC', ...allZones.filter(tz => tz !== 'UTC')];
} catch (e) {
  console.error('Intl API not supported');
  timezones = ['UTC'];
}

let timezoneCount = 0;
const maxZones = 5;
const minZones = 2;
let activeDropdown = null;
let selectedIndex = -1;
let currentZones = [];
let currentStartHour = 0;
let isMarkdownView = false;
let selectedHourIndices = new Set();
let lastSelectedIndex = null;
let debounceTimer = null;

const STORAGE_KEY = 'tzplanner_state';

function encodeBase64(str) {
  try {
    return btoa(encodeURIComponent(str));
  } catch (e) {
    return btoa(str);
  }
}

function decodeBase64(str) {
  try {
    return decodeURIComponent(atob(str));
  } catch (e) {
    try {
      return atob(str);
    } catch (e2) {
      return null;
    }
  }
}

function debounce(func, wait) {
  return function executedFunction(...args) {
    const later = () => {
      clearTimeout(debounceTimer);
      func(...args);
    };
    clearTimeout(debounceTimer);
    debounceTimer = setTimeout(later, wait);
  };
}

function saveState() {
  const rows = document.querySelectorAll('.timezone-row');
  const state = {
    zones: [],
    includeUtc: document.getElementById('includeUtc').checked,
    selectedHours: Array.from(selectedHourIndices)
  };

  rows.forEach(row => {
    const nameInput = row.querySelector('.name-input');
    const tzInput = row.querySelector('.tz-input');
    state.zones.push({
      name: nameInput.value,
      timezone: tzInput.dataset.value || tzInput.value
    });
  });

  localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
}

function loadState() {
  try {
    const saved = localStorage.getItem(STORAGE_KEY);
    if (!saved) return false;

    const state = JSON.parse(saved);
    if (!state.zones || state.zones.length < minZones) return false;

    document.getElementById('includeUtc').checked = state.includeUtc || false;
    if (state.selectedHours) {
      selectedHourIndices = new Set(state.selectedHours);
    }

    document.getElementById('inputs').innerHTML = '';
    timezoneCount = 0;

    state.zones.forEach(zone => {
      addTimezone(zone.name, zone.timezone);
    });

    return true;
  } catch (e) {
    return false;
  }
}

function loadFromUrl() {
  const params = new URLSearchParams(window.location.search);
  const dataParam = params.get('d');

  if (!dataParam) return false;

  try {
    const decoded = decodeBase64(dataParam);
    if (!decoded) return false;

    const data = JSON.parse(decoded);
    if (!data.zones || !Array.isArray(data.zones) || data.zones.length < minZones) return false;

    document.getElementById('inputs').innerHTML = '';
    timezoneCount = 0;

    data.zones.forEach(zone => {
      addTimezone(zone.name || '', zone.timezone || '');
    });

    document.getElementById('includeUtc').checked = data.utc || false;

    if (data.selected) {
      selectedHourIndices = new Set(data.selected);
    }

    setTimeout(() => generateTable(true), 100);

    return true;
  } catch (e) {
    console.error('Failed to load from URL:', e);
    return false;
  }
}

function showError(msg) {
  const errorEl = document.getElementById('errorContainer');
  errorEl.textContent = msg;
  errorEl.classList.add('visible');
  setTimeout(() => errorEl.classList.remove('visible'), 5000);
}

function clearError() {
  document.getElementById('errorContainer').classList.remove('visible');
}

function isOutputVisible() {
  return !document.getElementById('output').classList.contains('hidden');
}

function autoGenerate() {
  clearError();
  if (isOutputVisible() && timezoneCount >= minZones) {
    const rows = document.querySelectorAll('.timezone-row');
    let allFilled = true;
    for (const row of rows) {
      const tzInput = row.querySelector('.tz-input');
      const tz = tzInput.dataset.value || tzInput.value;
      if (!tz || !timezones.includes(tz)) {
        allFilled = false;
        break;
      }
    }
    if (allFilled) {
      saveState();
      generateTable(true);
    }
  }
}

const debouncedAutoGenerate = debounce(autoGenerate, 150);

function createTimezoneSearch(container, prefillName = '', prefillTz = '') {
  const wrapper = document.createElement('div');
  wrapper.className = 'tz-container';

  const input = document.createElement('input');
  input.type = 'text';
  input.className = 'tz-input';
  input.placeholder = 'Search timezone...';
  input.autocomplete = 'off';
  if (prefillTz) {
    input.value = prefillTz;
    input.dataset.value = prefillTz;
  }

  const dropdown = document.createElement('div');
  dropdown.className = 'tz-dropdown';

  wrapper.appendChild(input);
  wrapper.appendChild(dropdown);

  let filteredZones = [];

  function selectValue(tz) {
    input.value = tz;
    input.dataset.value = tz;
    dropdown.classList.remove('active');
    debouncedAutoGenerate();
  }

  input.addEventListener('input', (e) => {
    const query = e.target.value.toLowerCase();
    dropdown.innerHTML = '';
    selectedIndex = -1;

    if (query.length === 0) {
      dropdown.classList.remove('active');
      return;
    }

    filteredZones = timezones.filter(tz => tz.toLowerCase().includes(query));

    if (filteredZones.length === 0) {
      dropdown.classList.remove('active');
      return;
    }

    filteredZones.slice(0, 50).forEach((tz, index) => {
      const div = document.createElement('div');
      div.className = 'tz-option';
      div.textContent = tz;
      div.dataset.index = index;
      div.dataset.value = tz;
      div.addEventListener('click', () => selectValue(tz));
      dropdown.appendChild(div);
    });

    dropdown.classList.add('active');
    activeDropdown = dropdown;
  });

  input.addEventListener('keydown', (e) => {
    if (!dropdown.classList.contains('active')) return;

    const options = dropdown.querySelectorAll('.tz-option');

    if (e.key === 'ArrowDown') {
      e.preventDefault();
      selectedIndex = Math.min(selectedIndex + 1, options.length - 1);
      updateSelection(options);
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      selectedIndex = Math.max(selectedIndex - 1, -1);
      updateSelection(options);
    } else if (e.key === 'Enter') {
      e.preventDefault();
      if (selectedIndex >= 0 && options[selectedIndex]) {
        selectValue(options[selectedIndex].dataset.value);
      } else if (filteredZones.length > 0) {
        selectValue(filteredZones[0]);
      }
    } else if (e.key === 'Escape') {
      dropdown.classList.remove('active');
      selectedIndex = -1;
    }
  });

  input.addEventListener('blur', () => {
    setTimeout(() => {
      dropdown.classList.remove('active');
      if (!input.dataset.value && input.value) {
        const match = timezones.find(tz => tz.toLowerCase() === input.value.toLowerCase());
        if (match) {
          input.value = match;
          input.dataset.value = match;
          debouncedAutoGenerate();
        }
      }
    }, 200);
  });

  input.addEventListener('focus', () => {
    if (input.value.length > 0 && filteredZones.length > 0) {
      dropdown.classList.add('active');
    }
  });

  return { input, dropdown, wrapper };
}

function updateSelection(options) {
  options.forEach((opt, idx) => {
    if (idx === selectedIndex) {
      opt.classList.add('selected');
      opt.scrollIntoView({ block: 'nearest' });
    } else {
      opt.classList.remove('selected');
    }
  });
}

function addTimezone(prefillName = '', prefillTz = '') {
  if (timezoneCount >= maxZones) return;

  const container = document.getElementById('inputs');
  const row = document.createElement('div');
  row.className = 'timezone-row';
  row.dataset.index = timezoneCount;

  const nameInput = document.createElement('input');
  nameInput.type = 'text';
  nameInput.className = 'name-input';
  nameInput.placeholder = 'Name (optional)';
  nameInput.value = prefillName;
  nameInput.addEventListener('blur', debouncedAutoGenerate);

  const tzComponents = createTimezoneSearch(row, prefillName, prefillTz);

  const removeBtn = document.createElement('button');
  removeBtn.textContent = 'Remove';
  removeBtn.onclick = () => {
    row.remove();
    timezoneCount--;
    updateButtons();
    if (timezoneCount < minZones) {
      clearTable();
      localStorage.removeItem(STORAGE_KEY);
    } else {
      debouncedAutoGenerate();
    }
  };

  row.appendChild(nameInput);
  row.appendChild(tzComponents.wrapper);
  row.appendChild(removeBtn);
  container.appendChild(row);

  timezoneCount++;
  updateButtons();
  if (timezoneCount < minZones) clearTable();
}

function updateButtons() {
  document.getElementById('addBtn').disabled = timezoneCount >= maxZones;
}

function clearTable() {
  document.getElementById('output').classList.add('hidden');
  document.getElementById('toggleViewBtn').classList.add('hidden');
  isMarkdownView = false;
  selectedHourIndices.clear();
  lastSelectedIndex = null;
  updateToggleButton();
}

function clearAll() {
  document.getElementById('inputs').innerHTML = '';
  timezoneCount = 0;
  document.getElementById('includeUtc').checked = false;
  document.getElementById('urlDisplay').classList.add('hidden');
  localStorage.removeItem(STORAGE_KEY);
  clearTable();
  const browserTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
  for (let i = 0; i < minZones; i++) {
    if (i === 0 && browserTz && timezones.includes(browserTz)) {
      addTimezone('', browserTz);
    } else {
      addTimezone();
    }
  }
}

function getCurrentHourInTimezone(timezone) {
  const now = new Date();
  const timeString = now.toLocaleString('en-US', {
    timeZone: timezone,
    hour12: false,
    hour: 'numeric',
    minute: 'numeric',
    second: 'numeric'
  });
  const hour = parseInt(timeString.split(':')[0]);
  return hour;
}

function formatTimezoneHeader(name) {
  if (name === 'UTC') return 'UTC';
  const parts = name.split('/');
  return (parts[parts.length - 1] || name).replace(/_/g, ' ');
}

function generateTable(auto = false) {
  const rows = document.querySelectorAll('.timezone-row');
  const zones = [];

  for (const row of rows) {
    const nameInput = row.querySelector('.name-input');
    const tzInput = row.querySelector('.tz-input');
    const tz = tzInput.dataset.value || tzInput.value;
    const name = nameInput.value.trim() || tz;

    if (!tz) {
      if (!auto) showError('Please fill in all timezones');
      return;
    }

    if (!timezones.includes(tz)) {
      if (!auto) showError(`Invalid timezone: ${tz}`);
      return;
    }

    zones.push({ name, timezone: tz });
  }

  if (zones.length < minZones) {
    if (!auto) showError(`Please add at least ${minZones} timezones`);
    return;
  }

  if (document.getElementById('includeUtc').checked) {
    zones.unshift({ name: 'UTC', timezone: 'UTC' });
  }

  currentZones = zones;

  const firstZoneHour = getCurrentHourInTimezone(zones[0].timezone);
  currentStartHour = firstZoneHour;

  renderTable();

  document.getElementById('output').classList.remove('hidden');
  document.getElementById('toggleViewBtn').classList.remove('hidden');

  if (isMarkdownView) {
    document.getElementById('markdownView').textContent = generateMarkdown();
  }
}

function renderTable() {
  const headerRow = document.getElementById('headerRow');
  headerRow.innerHTML = '<th>Hour</th>';
  currentZones.forEach(z => {
    const th = document.createElement('th');
    th.textContent = z.name;
    headerRow.appendChild(th);
  });

  const tbody = document.getElementById('tableBody');
  tbody.innerHTML = '';

  const baseDate = new Date();
  baseDate.setMinutes(0, 0, 0);

  for (let i = 0; i < 24; i++) {
    const hour = (currentStartHour + i) % 24;
    const row = document.createElement('tr');
    row.dataset.hourIndex = i;

    if (selectedHourIndices.has(i)) {
      row.classList.add('selected');
    }

    row.addEventListener('click', (e) => handleRowClick(e, row, i));

    const hourCell = document.createElement('td');
    hourCell.className = 'hour-cell';
    hourCell.textContent = `${hour.toString().padStart(2, '0')}:00`;
    row.appendChild(hourCell);

    const currentBase = new Date(baseDate);
    currentBase.setHours(baseDate.getHours() + i);

    currentZones.forEach(zone => {
      const cell = document.createElement('td');
      const timeStr = currentBase.toLocaleTimeString('en-US', {
        timeZone: zone.timezone,
        hour: 'numeric',
        minute: '2-digit',
        hour12: true
      });

      const zoneDate = new Date(currentBase.toLocaleString('en-US', { timeZone: zone.timezone }));
      const baseDateCopy = new Date(currentBase.toLocaleString('en-US', { timeZone: currentZones[0].timezone }));
      const dayDiff = Math.round((zoneDate - baseDateCopy) / 86400000);

      let dayIndicator = '';
      if (dayDiff === 1) dayIndicator = ' <span class="date-indicator">(+1)</span>';
      else if (dayDiff === -1) dayIndicator = ' <span class="date-indicator">(-1)</span>';

      cell.innerHTML = timeStr + dayIndicator;
      row.appendChild(cell);
    });

    tbody.appendChild(row);
  }
}

function handleRowClick(e, row, index) {
  if (e.ctrlKey || e.metaKey) {
    if (selectedHourIndices.has(index)) {
      selectedHourIndices.delete(index);
      row.classList.remove('selected');
    } else {
      selectedHourIndices.add(index);
      row.classList.add('selected');
      lastSelectedIndex = index;
    }
  } else if (e.shiftKey && lastSelectedIndex !== null) {
    const start = Math.min(lastSelectedIndex, index);
    const end = Math.max(lastSelectedIndex, index);
    for (let i = start; i <= end; i++) {
      selectedHourIndices.add(i);
    }
    renderTable();
  } else {
    selectedHourIndices.clear();
    selectedHourIndices.add(index);
    lastSelectedIndex = index;
    renderTable();
  }
  saveState();
}

function generateMarkdown() {
  const baseDate = new Date();
  baseDate.setMinutes(0, 0, 0);

  const headers = currentZones.map(z => formatTimezoneHeader(z.name));
  const colWidths = headers.map(h => Math.max(h.length, 6));

  let markdown = '| Hour |';
  headers.forEach((h, i) => {
    markdown += ' ' + h.padEnd(colWidths[i]) + ' |';
  });
  markdown += '\n|------|';
  headers.forEach((_, i) => {
    markdown += '-'.repeat(colWidths[i] + 2) + '|';
  });
  markdown += '\n';

  for (let i = 0; i < 24; i++) {
    const hour = (currentStartHour + i) % 24;
    const hourStr = `${hour.toString().padStart(2, '0')}:00`;
    markdown += `| ${hourStr} |`;

    const currentBase = new Date(baseDate);
    currentBase.setHours(baseDate.getHours() + i);

    currentZones.forEach((zone, idx) => {
      const timeStr = currentBase.toLocaleTimeString('en-US', {
        timeZone: zone.timezone,
        hour: 'numeric',
        minute: '2-digit',
        hour12: true
      });

      const zoneDate = new Date(currentBase.toLocaleString('en-US', { timeZone: zone.timezone }));
      const baseDateCopy = new Date(currentBase.toLocaleString('en-US', { timeZone: currentZones[0].timezone }));
      const dayDiff = Math.round((zoneDate - baseDateCopy) / 86400000);

      let timeWithIndicator = timeStr;
      if (dayDiff === 1) timeWithIndicator += ' (+1)';
      else if (dayDiff === -1) timeWithIndicator += ' (-1)';

      const padded = timeWithIndicator.padEnd(colWidths[idx]);
      markdown += ` ${padded} |`;
    });
    markdown += '\n';
  }

  return markdown;
}

function toggleView() {
  const table = document.getElementById('timetable');
  const markdownContainer = document.getElementById('markdownContainer');

  if (isMarkdownView) {
    table.classList.remove('hidden');
    markdownContainer.classList.add('hidden');
    isMarkdownView = false;
  } else {
    document.getElementById('markdownView').textContent = generateMarkdown();
    table.classList.add('hidden');
    markdownContainer.classList.remove('hidden');
    isMarkdownView = true;
  }

  updateToggleButton();
}

function updateToggleButton() {
  const btn = document.getElementById('toggleViewBtn');
  if (isMarkdownView) {
    btn.textContent = 'Show Table';
  } else {
    btn.textContent = 'Generate as Markdown';
  }
}

function copyToClipboard() {
  const code = document.getElementById('markdownView').textContent;
  navigator.clipboard.writeText(code).then(() => {
    showError('Copied to clipboard!');
    setTimeout(clearError, 2000);
  }).catch(() => {
    const textarea = document.createElement('textarea');
    textarea.value = code;
    document.body.appendChild(textarea);
    textarea.select();
    document.execCommand('copy');
    document.body.removeChild(textarea);
    showError('Copied to clipboard!');
    setTimeout(clearError, 2000);
  });
}

function generateShareableUrl() {
  const rows = document.querySelectorAll('.timezone-row');
  const zones = [];

  for (const row of rows) {
    const nameInput = row.querySelector('.name-input');
    const tzInput = row.querySelector('.tz-input');
    const tz = tzInput.dataset.value || tzInput.value;
    if (tz) {
      zones.push({
        name: nameInput.value,
        timezone: tz
      });
    }
  }

  if (zones.length < minZones) {
    showError(`Please add at least ${minZones} timezones first`);
    return;
  }

  const data = {
    zones: zones,
    utc: document.getElementById('includeUtc').checked
  };

  if (selectedHourIndices.size > 0) {
    data.selected = Array.from(selectedHourIndices);
  }

  const jsonStr = JSON.stringify(data);
  const encoded = encodeBase64(jsonStr);

  const url = `${window.location.origin}${window.location.pathname}?d=${encoded}`;

  const urlDisplay = document.getElementById('urlDisplay');
  urlDisplay.textContent = url;
  urlDisplay.classList.remove('hidden');

  navigator.clipboard.writeText(url).then(() => {
    showError('URL copied to clipboard!');
    setTimeout(clearError, 3000);
  }).catch(() => {
    showError('URL generated (copy manually)');
  });
}

document.getElementById('includeUtc').addEventListener('change', debouncedAutoGenerate);

document.addEventListener('keydown', (e) => {
  if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
    e.preventDefault();
    generateTable();
  }
});

document.getElementById('loadingIndicator').classList.remove('hidden');
setTimeout(() => {
  document.getElementById('loadingIndicator').classList.add('hidden');
  if (!loadFromUrl() && !loadState()) {
    const browserTz = Intl.DateTimeFormat().resolvedOptions().timeZone;
    for (let i = 0; i < minZones; i++) {
      if (i === 0 && browserTz && timezones.includes(browserTz)) {
        addTimezone('', browserTz);
      } else {
        addTimezone();
      }
    }
  }
}, 100);

document.addEventListener('click', (e) => {
  if (!e.target.closest('.tz-container')) {
    document.querySelectorAll('.tz-dropdown').forEach(d => d.classList.remove('active'));
  }
});
</script>
</body>
</html>