js

Created: March 20, 2026 at 7:40 am

Expires: Never

Syntax: Plain Text

// ...existing code...

document.addEventListener('DOMContentLoaded', function () {
  const gearBtns = document.querySelectorAll('.create-top-gear');
  gearBtns.forEach(function(gearBtn) {
    gearBtn.addEventListener('click', async function () {
      try {
        await window.enablePushNotifications();
        showToast('Notifications enabled!');
      } catch (err) {
        showToast('Notification permission denied or error', true);
      }
    });
  });
});

let selectedPlatform = 'instagram';
let currentGuideStep = 0;
let isInstagramGuide = false;

async function apiRequest(url, options = {}) {
  const response = await fetch(url, {
    headers: { 'Content-Type': 'application/json' },
    ...options,
  });
  const payload = await response.json().catch(() => ({ ok: false, message: 'Invalid server response' }));
  if (!response.ok || !payload.ok) {
    throw new Error(payload.message || 'Request failed');
  }
  return payload.data;
  // FIX: closing brace was missing — return was outside the function body
}


function updatePrimaryButton() {
  const btn = document.getElementById('sharePresetBtn');

  if (!btn) return;
  btn.innerText = 'Share';
}
function showToast(message, isError = false) {
  const toast = document.getElementById('toast');
  if (!toast) return;

  if (window.__toastHideTimer) {
    clearTimeout(window.__toastHideTimer);
    window.__toastHideTimer = null;
  }

  toast.textContent = message;
  toast.classList.remove('hidden', 'toast-from-left', 'toast-from-right');
  void toast.offsetWidth;
  toast.classList.add(isError ? 'toast-from-left' : 'toast-from-right');
  toast.classList.toggle('border-emerald-500/30', !isError);
  toast.classList.toggle('bg-emerald-500/10', !isError);
  toast.classList.toggle('text-emerald-200', !isError);
  toast.classList.toggle('border-red-500/30', isError);
  toast.classList.toggle('bg-red-500/10', isError);
  toast.classList.toggle('text-red-200', isError);

  window.__toastHideTimer = setTimeout(() => {
    toast.classList.add('hidden');
    toast.classList.remove('toast-from-left', 'toast-from-right');
    window.__toastHideTimer = null;
  }, 3200);
}

const ONBOARDING_AVATAR_STORAGE_KEY = 'ngl_onboarding_avatar_data_url';
const READ_VIEW_IDS_STORAGE_KEY = 'ngl_read_view_ids';
const FCM_TOKEN_STORAGE_KEY = 'ngl_fcm_token';

// FIX: Centralised avatar data-URL validator — prevents XSS via crafted data URLs
// injected into CSS / canvas through localStorage.
function isValidImageDataUrl(value) {
  return typeof value === 'string' && /^data:image\/(png|jpeg|jpg|gif|webp);base64,/.test(value);
}

// FIX: blobToBase64 moved to module scope so it is accessible everywhere
// (was previously defined inside an event-handler, causing ReferenceError on reuse)
function blobToBase64(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result.split(',')[1]);
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}

async function initFirebasePush() {
  const firebaseConfig = window.APP_CONFIG?.firebase;
  const viewerId = Number(window.APP_CONFIG?.viewerId || 0);

  if (!firebaseConfig?.enabled || viewerId <= 0) return;
  if (!('serviceWorker' in navigator) || !('Notification' in window)) return;

  const hasRequiredWebConfig = [
    firebaseConfig?.web?.apiKey,
    firebaseConfig?.web?.projectId,
    firebaseConfig?.web?.messagingSenderId,
    firebaseConfig?.web?.appId,
    firebaseConfig?.vapidPublicKey,
  ].every((value) => String(value || '').trim() !== '');

  if (!hasRequiredWebConfig) return;

  let firebaseAppModule;
  let firebaseMessagingModule;

  const loadFirebaseModules = async () => {
    if (firebaseAppModule && firebaseMessagingModule) {
      return { firebaseAppModule, firebaseMessagingModule };
    }
    [firebaseAppModule, firebaseMessagingModule] = await Promise.all([
      import('https://www.gstatic.com/firebasejs/10.13.2/firebase-app.js'),
      import('https://www.gstatic.com/firebasejs/10.13.2/firebase-messaging.js'),
    ]);
    return { firebaseAppModule, firebaseMessagingModule };
  };

  const registerTokenWithBackend = async (token) => {
    await apiRequest(`${window.APP_CONFIG.baseUrl}/api/push/register-token`, {
      method: 'POST',
      body: JSON.stringify({ token, platform: 'web_twa_android' }),
    });
  };

  let foregroundHandlerBound = false;

  const syncFcmToken = async ({ requestPermission = false } = {}) => {
    const currentPermission = Notification.permission;
    if (requestPermission && currentPermission === 'default') {
      const nextPermission = await Notification.requestPermission();
      if (nextPermission !== 'granted') throw new Error('Notification permission not granted');
    } else if (currentPermission !== 'granted') {
      throw new Error('Notification permission not granted');
    }

    const base = String(window.APP_CONFIG?.baseUrl || '').replace(/\/$/, '');
    const swUrl = `${base}/sw.js`;
    const swScope = `${base}/`;
    let swRegistration = await navigator.serviceWorker.getRegistration(swScope);
    if (!swRegistration) {
      swRegistration = await navigator.serviceWorker.register(swUrl, { scope: swScope });
    }

    const { firebaseAppModule: appMod, firebaseMessagingModule: msgMod } = await loadFirebaseModules();
    const app = appMod.getApps().some((item) => item.name === 'ngl-firebase-app')
      ? appMod.getApp('ngl-firebase-app')
      : appMod.initializeApp(firebaseConfig.web, 'ngl-firebase-app');
    const messaging = msgMod.getMessaging(app);

    if (!foregroundHandlerBound) {
      msgMod.onMessage(messaging, (payload) => {
        const title = payload?.notification?.title || 'New anonymous message';
        const body = payload?.notification?.body || 'Someone sent you a message';
        showToast(title, false);
        if (document.visibilityState !== 'visible' && Notification.permission === 'granted') {
          new Notification(title, {
            body,
            icon: `${window.APP_CONFIG.baseUrl}/assets/icons/icon-192.png`,
          });
        }
      });
      foregroundHandlerBound = true;
    }

    const token = await msgMod.getToken(messaging, {
      vapidKey: firebaseConfig.vapidPublicKey,
      serviceWorkerRegistration: swRegistration,
    });

    if (!token) throw new Error('Unable to obtain FCM token');

    const previousToken = localStorage.getItem(FCM_TOKEN_STORAGE_KEY) || '';
    if (previousToken !== token) {
      await registerTokenWithBackend(token);
      localStorage.setItem(FCM_TOKEN_STORAGE_KEY, token);
    }

    return token;
  };

  window.enablePushNotifications = async () => {
    const token = await syncFcmToken({ requestPermission: true });
    showToast('Notifications enabled');
    return token;
  };

  if (Notification.permission === 'granted') {
    (async () => {
      try {
        await syncFcmToken({ requestPermission: false });
      } catch {
        // Keep app usable even if background token sync fails
      }
    })();
  }
}

function readLocalArray(key) {
  try {
    const value = JSON.parse(localStorage.getItem(key) || '[]');
    return Array.isArray(value) ? value : [];
  } catch {
    return [];
  }
}

function setDotVisibility(dotElements, visible) {
  dotElements.forEach((dot) => dot.classList.toggle('is-visible', visible));
}

function markViewAsRead(viewId) {
  const normalizedId = String(viewId || '').trim();
  if (!normalizedId) return;
  const readIds = readLocalArray(READ_VIEW_IDS_STORAGE_KEY).map((id) => String(id));
  if (!readIds.includes(normalizedId)) {
    readIds.push(normalizedId);
    localStorage.setItem(READ_VIEW_IDS_STORAGE_KEY, JSON.stringify(readIds));
  }
}

function applyRecentViewReadState() {
  const readSet = new Set(readLocalArray(READ_VIEW_IDS_STORAGE_KEY).map((id) => String(id)));
  const viewItems = document.querySelectorAll('.recent-view-item[data-view-id]');

  viewItems.forEach((item) => {
    const viewId = String(item.dataset.viewId || '').trim();
    const label = item.querySelector('[data-view-label]');
    const badge = item.querySelector('[data-view-badge]');
    const isRead = viewId !== '' && readSet.has(viewId);

    item.classList.toggle('is-read', isRead);
    if (badge) badge.classList.toggle('is-read', isRead);
    if (label) label.textContent = isRead ? 'Seen' : 'New view!';

    if (!item.dataset.readBound) {
      item.addEventListener('click', () => markViewAsRead(viewId));
      item.dataset.readBound = '1';
    }
  });

  const viewDetailShell = document.querySelector('.view-detail-shell[data-view-id]');
  if (viewDetailShell) {
    markViewAsRead(String(viewDetailShell.dataset.viewId || '').trim());
    syncHeaderNotificationDots();
  }
}

function formatRelativeTimeFromTimestamp(timestampSeconds) {
  const ts = Number(timestampSeconds || 0);
  if (!Number.isFinite(ts) || ts <= 0) return '';
  const nowSeconds = Math.floor(Date.now() / 1000);
  const diff = Math.max(0, nowSeconds - ts);
  if (diff < 60) return 'just now';
  if (diff < 3600) return `${Math.max(1, Math.floor(diff / 60))} min ago`;
  if (diff < 86400) return `${Math.max(1, Math.floor(diff / 3600))} hours ago`;
  return `${Math.max(1, Math.floor(diff / 86400))} days ago`;
}

function parseSqlDateTimeToUnix(datetimeValue) {
  const raw = String(datetimeValue || '').trim();
  if (!raw) return null;
  const match = raw.match(/^(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})(?::(\d{2}))?$/);
  if (!match) return null;
  const date = new Date(
    Number(match[1]),
    Number(match[2]) - 1,
    Number(match[3]),
    Number(match[4]),
    Number(match[5]),
    Number(match[6] || 0)
  );
  const timeMs = date.getTime();
  return Number.isFinite(timeMs) ? Math.floor(timeMs / 1000) : null;
}

function startRecentViewTimeTicker() {
  const timeNodes = Array.from(document.querySelectorAll('.recent-view-item[data-created-ts] [data-view-time]'));
  if (!timeNodes.length) return;
  const render = () => {
    timeNodes.forEach((timeNode) => {
      const item = timeNode.closest('.recent-view-item[data-created-ts]');
      if (!item) return;
      const fromRawDate = parseSqlDateTimeToUnix(item.dataset.createdAt);
      const baseTs = fromRawDate ?? Number(item.dataset.createdTs || 0);
      const nextLabel = formatRelativeTimeFromTimestamp(baseTs);
      if (nextLabel) timeNode.textContent = nextLabel;
    });
  };
  render();
  if (window.__recentViewTimeTicker) clearInterval(window.__recentViewTimeTicker);
  window.__recentViewTimeTicker = setInterval(render, 60000);
}

function startInboxMessageTimeTicker() {
  const timeNodes = Array.from(document.querySelectorAll('.inbox-feed-item[data-created-at] [data-msg-time]'));
  if (!timeNodes.length) return;
  const render = () => {
    timeNodes.forEach((timeNode) => {
      const item = timeNode.closest('.inbox-feed-item[data-created-at]');
      if (!item) return;
      const parsedTs = parseSqlDateTimeToUnix(item.dataset.createdAt);
      const nextLabel = formatRelativeTimeFromTimestamp(parsedTs ?? 0);
      if (nextLabel) timeNode.textContent = nextLabel;
    });
  };
  render();
  if (window.__inboxMessageTimeTicker) clearInterval(window.__inboxMessageTimeTicker);
  window.__inboxMessageTimeTicker = setInterval(render, 60000);
}

async function syncHeaderNotificationDots() {
  const inboxDots = Array.from(document.querySelectorAll('[data-header-inbox-dot]'));
  const eyeDots = Array.from(document.querySelectorAll('[data-header-eye-dot]'));
  if (!inboxDots.length && !eyeDots.length) return;

  let hasUnreadInbox = false;
  let hasUnseenViews = false;

  if (inboxDots.length) {
    try {
      const inboxData = await apiRequest(`${window.APP_CONFIG.baseUrl}/api/inbox`);
      const readSet = new Set(readLocalArray('ngl_read_msgs').map((id) => String(id)));
      const items = Array.isArray(inboxData)
        ? inboxData
        : (Array.isArray(inboxData?.items) ? inboxData.items : []);
      hasUnreadInbox = items.some((item) => !readSet.has(String(item?.id ?? '')));
    } catch {
      hasUnreadInbox = false;
    }
  }

  if (eyeDots.length) {
    try {
      const analyticsData = await apiRequest(`${window.APP_CONFIG.baseUrl}/api/analytics`);
      const totalViews = Number(analyticsData?.views ?? 0);
      const safeTotalViews = Number.isFinite(totalViews) && totalViews >= 0 ? totalViews : 0;
      const readViewCount = new Set(readLocalArray(READ_VIEW_IDS_STORAGE_KEY).map((id) => String(id))).size;
      hasUnseenViews = safeTotalViews > readViewCount;
    } catch {
      hasUnseenViews = false;
    }
  }

  setDotVisibility(inboxDots, hasUnreadInbox);
  setDotVisibility(eyeDots, hasUnseenViews);
}

syncHeaderNotificationDots();
applyRecentViewReadState();
startRecentViewTimeTicker();
startInboxMessageTimeTicker();
initFirebasePush();

const twaOnboarding = document.getElementById('twaOnboarding');
if (twaOnboarding) {
  const stepUsername = document.getElementById('twaStepUsername');
  const stepAvatar = document.getElementById('twaStepAvatar');
  const skipBtn = document.getElementById('twaSkipBtn');
  const usernameForm = document.getElementById('twaCreateUserForm');
  const usernameInput = usernameForm?.querySelector('input[name="username"]');
  const avatarInput = document.getElementById('twaAvatarInput');
  const avatarPickBtn = document.getElementById('twaAvatarPickBtn');
  const avatarPreview = document.getElementById('twaAvatarPreview');
  const avatarPlaceholder = document.getElementById('twaAvatarPlaceholder');
  const avatarContinueBtn = document.getElementById('twaAvatarContinueBtn');

  let activeStep = 'username';
  let selectedAvatarDataUrl = '';

  const setStep = (step) => {
    activeStep = step;
    if (stepUsername) stepUsername.classList.toggle('hidden', step !== 'username');
    if (stepAvatar) stepAvatar.classList.toggle('hidden', step !== 'avatar');
    if (skipBtn) skipBtn.classList.toggle('hidden', step !== 'avatar');
  };

  const goToCreateQuestion = () => {
    // FIX: validate data URL before writing to localStorage to prevent XSS
    if (selectedAvatarDataUrl && isValidImageDataUrl(selectedAvatarDataUrl)) {
      localStorage.setItem(ONBOARDING_AVATAR_STORAGE_KEY, selectedAvatarDataUrl);
    } else {
      localStorage.removeItem(ONBOARDING_AVATAR_STORAGE_KEY);
    }
    location.href = `${window.APP_CONFIG.baseUrl}/create-question`;
  };

  if (usernameInput) {
    usernameInput.addEventListener('input', () => {
      const normalized = String(usernameInput.value || '').toLowerCase().replace(/\s+/g, '');
      if (usernameInput.value !== normalized) usernameInput.value = normalized;
    });
  }

  if (usernameForm) {
    usernameForm.addEventListener('submit', async (event) => {
      event.preventDefault();
      const formData = new FormData(usernameForm);
      const normalizedUsername = String(formData.get('username') || '').toLowerCase().trim();
      if (usernameInput) usernameInput.value = normalizedUsername;
      try {
        await apiRequest(`${window.APP_CONFIG.baseUrl}/api/users`, {
          method: 'POST',
          body: JSON.stringify({ username: normalizedUsername }),
        });
        setStep('avatar');
      } catch (error) {
        showToast(error.message, true);
      }
    });
  }

  if (avatarPickBtn && avatarInput) {
    avatarPickBtn.addEventListener('click', () => avatarInput.click());
  }

  if (avatarInput) {
    avatarInput.addEventListener('change', () => {
      const file = avatarInput.files && avatarInput.files[0];
      if (!file) return;
      // FIX: validate MIME type before reading to prevent non-image files being stored
      if (!file.type.startsWith('image/')) {
        showToast('Please select a valid image file', true);
        return;
      }
      const reader = new FileReader();
      reader.onload = () => {
        if (typeof reader.result !== 'string') return;
        selectedAvatarDataUrl = reader.result;
        if (avatarPreview) {
          avatarPreview.src = selectedAvatarDataUrl;
          avatarPreview.classList.remove('hidden');
        }
        if (avatarPlaceholder) avatarPlaceholder.classList.add('hidden');
      };
      reader.readAsDataURL(file);
      avatarInput.value = '';
    });
  }

  if (avatarContinueBtn) {
    avatarContinueBtn.addEventListener('click', () => goToCreateQuestion());
  }

  if (skipBtn) {
    skipBtn.addEventListener('click', () => {
      selectedAvatarDataUrl = '';
      goToCreateQuestion();
    });
  }

  setStep('username');
}

const createUserForm = document.getElementById('createUserForm');
if (createUserForm) {
  const usernameInput = createUserForm.querySelector('input[name="username"]');

  if (usernameInput) {
    usernameInput.addEventListener('input', () => {
      const normalized = String(usernameInput.value || '').toLowerCase().replace(/\s+/g, '');
      if (usernameInput.value !== normalized) usernameInput.value = normalized;
    });
  }

  createUserForm.addEventListener('submit', async (event) => {
    event.preventDefault();
    const formData = new FormData(createUserForm);
    const normalizedUsername = String(formData.get('username') || '').toLowerCase().trim();
    if (usernameInput) usernameInput.value = normalizedUsername;
    try {
      await apiRequest(`${window.APP_CONFIG.baseUrl}/api/users`, {
        method: 'POST',
        body: JSON.stringify({ username: normalizedUsername }),
      });
      location.href = `${window.APP_CONFIG.baseUrl}/create-question`;
    } catch (error) {
      showToast(error.message, true);
    }
  });
}

const createQuestionForm = document.getElementById('createQuestionForm');

// ================= INSTAGRAM GUIDE =================
const instaSteps = [
  { text: 'Click the sticker button',   html: `<img src="/assets/insta-step1.png" style="width:100%;border-radius:12px;">` },
  { text: 'Tap on LINK sticker',        html: `<img src="/assets/insta-step2.png" style="width:100%;border-radius:12px;">` },
  { text: 'Paste your copied link',     html: `<img src="/assets/insta-step3.jpeg" style="width:100%;border-radius:12px;">` },
  { text: 'Share your story 🚀',        html: `<img src="/assets/insta-step4.png" style="width:100%;border-radius:12px;">` },
];

function renderGuideStep() {
  const step = instaSteps[currentGuideStep];
  document.getElementById('guideText').innerText = step.text;
  document.getElementById('guideVisual').innerHTML = step.html;
  document.querySelectorAll('#shareGuideSteps .step').forEach((el, i) => {
    el.classList.toggle('active', i === currentGuideStep);
  });
}

function renderPlatformGuide(platform) {
  const titleEl = document.querySelector('#shareGuideModal .share-guide-title');
  const visualEl = document.getElementById('guideVisual');
  const textEl = document.getElementById('guideText');
  const primaryBtn = document.getElementById('shareGuideWhatsappBtn');
  const stepsEl = document.getElementById('shareGuideSteps');

  if (!visualEl || !textEl || !primaryBtn) return;

  if (platform === 'instagram') {
    titleEl.textContent = 'How to add the Link to your story';
    stepsEl?.classList.remove('hidden');
    renderGuideStep();
    primaryBtn.innerText = currentGuideStep < instaSteps.length - 1 ? 'Next' : 'Share on Instagram';
    updatePrimaryButton();
    return;
  }

  stepsEl?.classList.add('hidden');

  if (platform === 'snapchat') {
    titleEl.textContent = 'Share to Snapchat';
    visualEl.innerHTML = `
      <div class="share-guide-visual-topbar">
        <span>Stories</span>
        <span class="share-guide-visual-search">Snapchat</span>
      </div>
      <div class="share-guide-visual-card">
        <div class="share-guide-status-icon">
          <span style="font-size: 26px;">👻</span>
        </div>
        <div class="share-guide-status-copy">
          <strong>My Story</strong>
          <span>Share the story image first</span>
        </div>
        <div class="share-guide-status-dots">•••</div>
      </div>
      <div class="share-guide-hand">☟</div>
    `;
    textEl.innerHTML = 'Open Snapchat and post the story image, then add or paste your copied link in the snap.';
    primaryBtn.innerText = 'Share on Snapchat';
  } else {
    titleEl.textContent = 'Share to WhatsApp Status';
    visualEl.innerHTML = `
      <div class="share-guide-visual-topbar">
        <span>Cancel</span>
        <span class="share-guide-visual-search">Search</span>
      </div>
      <div class="share-guide-visual-card">
        <div class="share-guide-status-icon">
          <span class="share-guide-status-clock"></span>
        </div>
        <div class="share-guide-status-copy">
          <strong>My Status</strong>
          <span>My contacts</span>
        </div>
        <div class="share-guide-status-dots">•••</div>
      </div>
      <div class="share-guide-hand">☟</div>
    `;
    textEl.innerHTML = 'Choose <strong>My Status</strong> inside WhatsApp, then add the copied link on your story.';
    primaryBtn.innerText = 'Share on WhatsApp!';
  }

  updatePrimaryButton();
}

function setSelectedSharePlatform(platform) {
  selectedPlatform = ['instagram', 'snapchat', 'whatsapp'].includes(platform) ? platform : 'instagram';
  isInstagramGuide = selectedPlatform === 'instagram';
  currentGuideStep = 0;
  document.querySelectorAll('#shareGuideModal .platform-tab').forEach((tab) => {
    tab.classList.toggle('active', tab.dataset.platform === selectedPlatform);
  });
  renderPlatformGuide(selectedPlatform);
}

function resetGuideModalUI() {
  setSelectedSharePlatform('instagram');
}

// ================= SHARED CANVAS HELPERS =================
// FIX: deduplicated — was defined twice with slightly different signatures

/**
 * Draws a rounded rectangle path without filling/stroking.
 * Call ctx.fill() / ctx.stroke() after as needed.
 */
function drawRoundedRectPath(ctx, x, y, w, h, r) {
  const rr = Math.min(r, w / 2, h / 2);
  ctx.beginPath();
  ctx.moveTo(x + rr, y);
  ctx.arcTo(x + w, y, x + w, y + h, rr);
  ctx.arcTo(x + w, y + h, x, y + h, rr);
  ctx.arcTo(x, y + h, x, y, rr);
  ctx.arcTo(x, y, x + w, y, rr);
  ctx.closePath();
}

function drawCoverImage(ctx, image, x, y, width, height) {
  const scale = Math.max(width / image.width, height / image.height);
  const drawWidth = image.width * scale;
  const drawHeight = image.height * scale;
  const offsetX = x + (width - drawWidth) / 2;
  const offsetY = y + (height - drawHeight) / 2;
  ctx.drawImage(image, offsetX, offsetY, drawWidth, drawHeight);
}

function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
  const words = text.split(' ');
  let line = '';
  const lines = [];
  words.forEach((word) => {
    const testLine = line + word + ' ';
    if (ctx.measureText(testLine).width > maxWidth) {
      lines.push(line);
      line = word + ' ';
    } else {
      line = testLine;
    }
  });
  lines.push(line);
  lines.forEach((l, i) => ctx.fillText(l.trim(), x, y + i * lineHeight));
}

function loadImageFromSource(src) {
  return new Promise((resolve, reject) => {
    if (!src) { reject(new Error('Missing image source')); return; }
    const image = new Image();
    image.crossOrigin = 'anonymous';
    image.onload = () => resolve(image);
    image.onerror = () => reject(new Error('Unable to load image'));
    image.src = src;
  });
}

// ================= CREATE QUESTION PAGE =================
if (createQuestionForm) {
  const templateButtons = document.querySelectorAll('.template-card');
  const templateRail = document.getElementById('templateCards');
  const presetScrollPrev = document.getElementById('presetScrollPrev');
  const presetScrollNext = document.getElementById('presetScrollNext');
  const questionText = document.getElementById('questionText');
  const templateKey = document.getElementById('templateKey');
  const presetActions = document.getElementById('presetActions');
  const customTemplateCard = document.getElementById('customTemplateCard');
  const customCardQuestionInput = document.getElementById('customCardQuestionInput');
  const customCardAvatar = document.getElementById('customCardAvatar');
  const customPromptDice = document.getElementById('customPromptDice');
  const avatarUploadInput = document.getElementById('avatarUploadInput');
  const avatarSlots = document.querySelectorAll('[data-avatar-key]');
  const copyPresetLinkBtn = document.getElementById('copyPresetLinkBtn');
  const sharePresetBtn = document.getElementById('sharePresetBtn');
  const shareGuideModal = document.getElementById('shareGuideModal');
  const shareGuideWhatsappBtn = document.getElementById('shareGuideWhatsappBtn');
  const shareGuideCloseEls = document.querySelectorAll('[data-share-guide-close]');
  const sharePlatformTabs = document.querySelectorAll('#shareGuideModal .platform-tab');
  const shareGuideSteps = document.querySelectorAll('#shareGuideSteps .step');
  const presetPublicLinkText = document.getElementById('presetPublicLinkText');

  let selectedTemplateValue = '';
  let selectedIsCustom = true;
  let generatedForCurrentTemplate = null;
  let pendingShareGuideData = null;

  const welcomeBotTriggeredQuestionIds = new Set();
  const welcomeBotPendingQuestionIds = new Set();

  // FIX: debounce guard for copy/share buttons to prevent duplicate API calls on rapid clicks
  let actionInFlight = false;

  let autoLinkRequestToken = 0;
  let customDraftValue = '';
  let pendingAvatarKey = 'custom';
  const avatarDataByKey = {};

  const predefinedPromptTexts = Array.from(templateButtons)
    .filter((button) => button.dataset.custom !== '1')
    .map((button) => String(button.dataset.value || '').trim())
    .filter((text) => text.length > 0);

  let customPromptIndex = predefinedPromptTexts.length > 0
    ? Math.floor(Math.random() * predefinedPromptTexts.length)
    : 0;
  let customPromptRotateTimer = null;
  let customPromptFadeTimer = null;

  const setCustomPromptPlaceholder = (withFade = false) => {
    if (!customCardQuestionInput || predefinedPromptTexts.length === 0) return;
    const nextPlaceholder = predefinedPromptTexts[customPromptIndex];
    if (!withFade || String(customCardQuestionInput.value || '').trim() !== '') {
      customCardQuestionInput.placeholder = nextPlaceholder;
      return;
    }
    if (customPromptFadeTimer) { clearTimeout(customPromptFadeTimer); customPromptFadeTimer = null; }
    customCardQuestionInput.classList.add('is-placeholder-fading');
    customPromptFadeTimer = setTimeout(() => {
      customCardQuestionInput.placeholder = nextPlaceholder;
      customCardQuestionInput.classList.remove('is-placeholder-fading');
      customPromptFadeTimer = null;
    }, 140);
  };

  const rotateCustomPromptPlaceholder = () => {
    if (!customCardQuestionInput || predefinedPromptTexts.length === 0) return;
    if (String(customCardQuestionInput.value || '').trim() !== '') return;
    customPromptIndex = (customPromptIndex + 1) % predefinedPromptTexts.length;
    setCustomPromptPlaceholder(true);
  };

  const startCustomPromptRotation = () => {
    if (!customCardQuestionInput || predefinedPromptTexts.length === 0) return;
    if (customPromptRotateTimer) clearInterval(customPromptRotateTimer);
    customPromptRotateTimer = setInterval(rotateCustomPromptPlaceholder, 2000);
    customCardQuestionInput.classList.add('is-rotating');
  };

  const stopCustomPromptRotation = () => {
    if (customPromptRotateTimer) { clearInterval(customPromptRotateTimer); customPromptRotateTimer = null; }
    if (customPromptFadeTimer) { clearTimeout(customPromptFadeTimer); customPromptFadeTimer = null; }
    if (customCardQuestionInput) {
      customCardQuestionInput.classList.remove('is-placeholder-fading', 'is-rotating');
    }
  };

  const applyAvatarToSlot = (key, dataUrl) => {
    if (!key || !dataUrl) return;
    document.querySelectorAll(`[data-avatar-key="${key}"]`).forEach((slot) => {
      const img = slot.querySelector('[data-avatar-image]');
      if (!img) return;
      img.src = dataUrl;
      slot.classList.add('has-image');
    });
  };

  const updateCustomQuestion = (value) => {
    if (!customCardQuestionInput) return;
    const safeValue = String(value ?? '');
    if (customCardQuestionInput.value !== safeValue) customCardQuestionInput.value = safeValue;
    if (questionText) questionText.value = safeValue;
  };

  const selectCustomTemplate = () => {
    selectedIsCustom = true;
    if (templateKey) templateKey.value = '';
    updateCustomQuestion(customDraftValue);
    templateButtons.forEach((item) => item.classList.remove('is-selected'));
    if (customTemplateCard) {
      customTemplateCard.classList.add('is-selected');
      updatePresetActionsTheme(customTemplateCard);
    }
    selectedTemplateValue = questionText?.value || '';
    generatedForCurrentTemplate = null;
    showPresetActions();
  };

  const hidePresetActions = () => {
    if (presetActions) presetActions.classList.add('hidden');
    if (presetPublicLinkText) presetPublicLinkText.textContent = 'Link will appear after generation';
  };

  const showPresetActions = () => {
    if (presetActions) presetActions.classList.remove('hidden');
  };

  const updatePresetActionsTheme = (button) => {
    if (!presetActions || !button) return;
    const computed = getComputedStyle(button);
    const start = computed.getPropertyValue('--card-start').trim() || '#14a7ff';
    const mid = computed.getPropertyValue('--card-mid').trim() || start;
    const end = computed.getPropertyValue('--card-end').trim() || '#5647f8';
    presetActions.style.setProperty('--preset-card-start', start);
    presetActions.style.setProperty('--preset-card-mid', mid);
    presetActions.style.setProperty('--preset-card-end', end);
  };

  const templateSignature = () => `${templateKey?.value || ''}::${questionText?.value || ''}`;

  const openShareGuide = (data) => {
    if (!shareGuideModal) return;
    resetGuideModalUI();
    pendingShareGuideData = data;
    shareGuideModal.classList.remove('hidden');
    shareGuideModal.setAttribute('aria-hidden', 'false');
    document.body.classList.add('share-guide-open');
  };

  const closeShareGuide = () => {
    if (!shareGuideModal) return;
    shareGuideModal.classList.add('hidden');
    shareGuideModal.setAttribute('aria-hidden', 'true');
    document.body.classList.remove('share-guide-open');
  };

async function shareInstagramStory(data) {
  try {
    await copyToClipboard(data.public_url);
    await new Promise((resolve) => setTimeout(resolve, 300));
    closeShareGuide();

    const blob = await buildInstagramStoryBlob(data);

    if (window.Capacitor?.Plugins?.InstagramShare) {
      try {
        await syncSpecificStoryImageToServer(data, blob, 'instagram-story.png');
        await window.Capacitor.Plugins.InstagramShare.shareToStory({
          imageUrl: data.story_image_url || '',
          contentUrl: data.public_url || 'https://pingxo.com/app',
        });
        showToast('Instagram Story share started!');
      } catch (error) {
        console.error('InstagramShare plugin error:', error);
        showToast('Instagram share failed: ' + (error?.message || 'Unknown error'), true);
        // Fallback: show download prompt, do NOT redirect
        if (blob) {
          // Show modal or download image
          showDownloadModal(blob, data.public_url);
        }
      }
    } else {
      // Fallback for web
      const url = URL.createObjectURL(blob);
      window.open(url, '_blank');
      showToast('Download image -> open Instagram -> add to story');
    }

    setTimeout(() => showToast('Link copied - tap sticker -> paste'), 800);
    triggerWelcomeBotOnce(data.question_id);
  } catch (error) {
    console.error('shareInstagramStory error:', error);
    showToast('Something went wrong: ' + (error?.message || 'Unknown error'), true);
  }
}

  const refreshLinkForSelection = ({ autoGeneratePreset = true } = {}) => {
    const questionValue = String(questionText?.value || '').trim();
    if (!presetPublicLinkText) return;
    if (!questionValue) {
      presetPublicLinkText.textContent = 'Type your own question';
      return;
    }
    if (selectedIsCustom) {
      const signature = templateSignature();
      presetPublicLinkText.textContent =
        generatedForCurrentTemplate && generatedForCurrentTemplate.signature === signature
          ? generatedForCurrentTemplate.data.public_url
          : 'Tap Copy Link to generate';
      return;
    }
    if (!autoGeneratePreset) {
      presetPublicLinkText.textContent = 'Tap Copy Link to generate';
      return;
    }
    const token = ++autoLinkRequestToken;
    presetPublicLinkText.textContent = 'Updating link...';
    ensureQuestionGenerated()
      .then((data) => { if (token === autoLinkRequestToken) presetPublicLinkText.textContent = data.public_url; })
      .catch(() => { if (token === autoLinkRequestToken) presetPublicLinkText.textContent = 'Tap Copy Link to generate'; });
  };

  const clearTemplateSelection = () => {
    selectedIsCustom = false;
    if (templateKey) templateKey.value = '';
    templateButtons.forEach((item) => item.classList.remove('is-selected'));
    generatedForCurrentTemplate = null;
    showPresetActions();
  };

  if (customTemplateCard) {
    customTemplateCard.addEventListener('click', () => {
      selectCustomTemplate();
      refreshLinkForSelection();
      if (customCardQuestionInput) customCardQuestionInput.focus();
    });
  }

  templateButtons.forEach((button) => {
    button.addEventListener('click', () => {
      if (button.dataset.custom === '1') {
        selectCustomTemplate();
        refreshLinkForSelection();
        if (customCardQuestionInput) customCardQuestionInput.focus();
        return;
      }
      selectPresetTemplate(button, { autoGenerateLink: true });
      if (questionText) questionText.focus();
    });
  });

  const selectPresetTemplate = (button, { autoGenerateLink = true } = {}) => {
    if (!button || button.dataset.custom === '1') return;
    customDraftValue = customCardQuestionInput?.value || customDraftValue;
    questionText.value = button.dataset.value || '';
    selectedTemplateValue = button.dataset.value || '';
    selectedIsCustom = false;
    if (templateKey) templateKey.value = button.dataset.key || '';
    templateButtons.forEach((item) => item.classList.remove('is-selected'));
    button.classList.add('is-selected');
    updatePresetActionsTheme(button);
    generatedForCurrentTemplate = null;
    showPresetActions();
    refreshLinkForSelection({ autoGeneratePreset: autoGenerateLink });
  };

  if (questionText) {
    questionText.addEventListener('input', () => {
      if (selectedIsCustom) {
        selectedTemplateValue = questionText.value;
        updateCustomQuestion(questionText.value);
        return;
      }
      if (questionText.value !== selectedTemplateValue) clearTemplateSelection();
    });
  }

  if (customCardQuestionInput) {
    setCustomPromptPlaceholder();
    startCustomPromptRotation();

    customCardQuestionInput.addEventListener('focus', () => {
      selectCustomTemplate();
      refreshLinkForSelection();
    });

    customCardQuestionInput.addEventListener('input', () => {
      const text = customCardQuestionInput.value || '';
      if (String(text).trim() !== '') stopCustomPromptRotation();
      else startCustomPromptRotation();

      const capped = text.length > 255 ? text.slice(0, 255) : text;
      if (capped !== text) customCardQuestionInput.value = capped;
      if (questionText) questionText.value = capped;
      customDraftValue = capped;
      selectedTemplateValue = capped;
      selectCustomTemplate();
      refreshLinkForSelection();
    });
  }

  if (customPromptDice && customCardQuestionInput) {
    customPromptDice.addEventListener('click', (event) => {
      event.preventDefault();
      event.stopPropagation();
      if (predefinedPromptTexts.length === 0) return;
      customPromptIndex = (customPromptIndex + 1) % predefinedPromptTexts.length;
      const nextPrompt = predefinedPromptTexts[customPromptIndex];
      customCardQuestionInput.value = nextPrompt;
      customCardQuestionInput.dispatchEvent(new Event('input', { bubbles: true }));
      customCardQuestionInput.focus();
      stopCustomPromptRotation();
    });
  }

  avatarSlots.forEach((slot) => {
    slot.addEventListener('click', (event) => {
      event.preventDefault();
      event.stopPropagation();
      pendingAvatarKey = slot.getAttribute('data-avatar-key') || 'custom';
      const parentCard = slot.closest('.template-card');
      if (parentCard) parentCard.click();
      if (avatarUploadInput) avatarUploadInput.click();
    });
  });

  if (avatarUploadInput) {
    avatarUploadInput.addEventListener('change', () => {
      const file = avatarUploadInput.files && avatarUploadInput.files[0];
      if (!file) return;
      // FIX: validate MIME type before reading
      if (!file.type.startsWith('image/')) {
        showToast('Please select a valid image file', true);
        return;
      }
      const reader = new FileReader();
      reader.onload = () => {
        if (typeof reader.result !== 'string') return;
        const uploadedAvatarDataUrl = reader.result;
        // FIX: validate data URL before applying to CSS / canvas
        if (!isValidImageDataUrl(uploadedAvatarDataUrl)) return;

        const allAvatarKeys = new Set(
          Array.from(document.querySelectorAll('[data-avatar-key]'))
            .map((el) => el.getAttribute('data-avatar-key') || '')
            .filter(Boolean)
        );
        allAvatarKeys.forEach((key) => {
          avatarDataByKey[key] = uploadedAvatarDataUrl;
          applyAvatarToSlot(key, uploadedAvatarDataUrl);
        });

        localStorage.setItem(ONBOARDING_AVATAR_STORAGE_KEY, uploadedAvatarDataUrl);
        generatedForCurrentTemplate = null;

        if (customCardAvatar) customCardAvatar.src = uploadedAvatarDataUrl;
        if (customTemplateCard) {
          customTemplateCard.style.setProperty('--custom-avatar-bg', `url('${uploadedAvatarDataUrl}')`);
          customTemplateCard.classList.add('has-avatar-bg');
        }

        pendingAvatarKey = 'custom';
        selectCustomTemplate();
        refreshLinkForSelection();
      };
      reader.readAsDataURL(file);
      avatarUploadInput.value = '';
    });
  }

  const onboardingAvatarDataUrl = localStorage.getItem(ONBOARDING_AVATAR_STORAGE_KEY) || '';
  // FIX: validate before applying stored avatar to CSS / canvas
  if (isValidImageDataUrl(onboardingAvatarDataUrl)) {
    const allAvatarKeys = new Set(
      Array.from(document.querySelectorAll('[data-avatar-key]'))
        .map((el) => el.getAttribute('data-avatar-key') || '')
        .filter(Boolean)
    );
    allAvatarKeys.forEach((key) => {
      avatarDataByKey[key] = onboardingAvatarDataUrl;
      applyAvatarToSlot(key, onboardingAvatarDataUrl);
    });
    if (customCardAvatar) customCardAvatar.src = onboardingAvatarDataUrl;
    if (customTemplateCard) {
      customTemplateCard.style.setProperty('--custom-avatar-bg', `url('${onboardingAvatarDataUrl}')`);
      customTemplateCard.classList.add('has-avatar-bg');
    }
    pendingAvatarKey = 'custom';
    selectCustomTemplate();
    refreshLinkForSelection();
  }

  showPresetActions();
  customDraftValue = customCardQuestionInput?.value || '';
  updateCustomQuestion(questionText?.value || '');
  refreshLinkForSelection();

  if (templateRail) {
    const step = 260;
    const templateSnapDebounceMs = 70;
    const templateSnapSmoothLockMs = 170;
    let templateScrollSyncTimer = null;
    let isSnappingTemplateRail = false;

    const getClosestTemplateToCenter = () => {
      if (!templateButtons.length) return null;
      const railRect = templateRail.getBoundingClientRect();
      const railCenterX = railRect.left + railRect.width / 2;
      let closest = null;
      let minDistance = Number.POSITIVE_INFINITY;
      templateButtons.forEach((button) => {
        const rect = button.getBoundingClientRect();
        const centerX = rect.left + rect.width / 2;
        const distance = Math.abs(centerX - railCenterX);
        if (distance < minDistance) { minDistance = distance; closest = button; }
      });
      return closest;
    };

    const syncSelectionWithRail = () => {
      const closestButton = getClosestTemplateToCenter();
      if (!closestButton || closestButton.classList.contains('is-selected')) return;
      if (closestButton.dataset.custom === '1') {
        selectCustomTemplate();
        refreshLinkForSelection();
      } else {
        selectPresetTemplate(closestButton, { autoGenerateLink: true });
      }
    };

    const snapRailToButton = (button, behavior = 'smooth') => {
      if (!button) return;
      const railRect = templateRail.getBoundingClientRect();
      const buttonRect = button.getBoundingClientRect();
      const delta = (buttonRect.left + buttonRect.width / 2) - (railRect.left + railRect.width / 2);
      if (Math.abs(delta) < 1) return;
      isSnappingTemplateRail = true;
      templateRail.scrollTo({ left: templateRail.scrollLeft + delta, behavior });
      setTimeout(() => { isSnappingTemplateRail = false; }, behavior === 'smooth' ? templateSnapSmoothLockMs : 0);
    };

    const scheduleRailSelectionSync = () => {
      if (templateScrollSyncTimer) clearTimeout(templateScrollSyncTimer);
      templateScrollSyncTimer = setTimeout(() => {
        templateScrollSyncTimer = null;
        snapRailToButton(getClosestTemplateToCenter(), 'smooth');
        syncSelectionWithRail();
      }, templateSnapDebounceMs);
    };

    const handleRailScroll = () => {
      if (isSnappingTemplateRail) return;
      if (isMobileRail() && isTouchingRail) return;
      syncSelectionWithRail();
      scheduleRailSelectionSync();
    };

    if (presetScrollPrev) {
      presetScrollPrev.addEventListener('click', () => {
        templateRail.scrollBy({ left: -step, behavior: 'smooth' });
        scheduleRailSelectionSync();
      });
    }
    if (presetScrollNext) {
      presetScrollNext.addEventListener('click', () => {
        templateRail.scrollBy({ left: step, behavior: 'smooth' });
        scheduleRailSelectionSync();
      });
    }

    const isMobileRail = () => window.matchMedia('(max-width: 768px)').matches;
    const swipeProgressThreshold = 0.38;
    const swipeVelocityThreshold = 0.65;
    let touchStartX = 0;
    let touchEndX = 0;
    let touchStartTime = 0;
    let touchStartIndex = 0;
    let isTouchingRail = false;

    const getSelectedCardIndex = () => {
      const idx = Array.from(templateButtons).findIndex((b) => b.classList.contains('is-selected'));
      return idx >= 0 ? idx : 0;
    };

    const getCenteredCardIndex = () => {
      const closest = getClosestTemplateToCenter();
      if (!closest) return getSelectedCardIndex();
      const idx = Array.from(templateButtons).indexOf(closest);
      return idx >= 0 ? idx : getSelectedCardIndex();
    };

    const scrollToCardByIndex = (index, behavior = 'smooth') => {
      if (!templateButtons.length) return;
      const safeIndex = Math.max(0, Math.min(templateButtons.length - 1, index));
      const targetCard = templateButtons[safeIndex];
      if (!targetCard) return;
      snapRailToButton(targetCard, behavior);
      if (targetCard.dataset.custom === '1') {
        selectCustomTemplate();
        refreshLinkForSelection();
      } else {
        selectPresetTemplate(targetCard, { autoGenerateLink: true });
      }
    };

    const handleTouchStart = (event) => {
      if (!isMobileRail() || !event.touches || event.touches.length === 0) return;
      isTouchingRail = true;
      touchStartX = event.touches[0].clientX;
      touchEndX = touchStartX;
      touchStartTime = performance.now();
      touchStartIndex = getCenteredCardIndex();
    };

    const handleTouchMove = (event) => {
      if (!isMobileRail() || !event.touches || event.touches.length === 0) return;
      touchEndX = event.touches[0].clientX;
    };

    const handleTouchEnd = () => {
      isTouchingRail = false;
      if (!isMobileRail()) { scheduleRailSelectionSync(); return; }
      const deltaX = touchEndX - touchStartX;
      const dragDistance = Math.abs(deltaX);
      const elapsedMs = Math.max(1, performance.now() - touchStartTime);
      const swipeVelocity = dragDistance / elapsedMs;
      const startCard = templateButtons[touchStartIndex] || null;
      const cardWidth = startCard
        ? startCard.getBoundingClientRect().width
        : Math.max(templateRail.clientWidth * 0.85, 1);
      const requiredDistance = Math.max(54, cardWidth * swipeProgressThreshold);
      const isIntentionalSwipe = dragDistance >= requiredDistance || swipeVelocity >= swipeVelocityThreshold;
      if (!isIntentionalSwipe) { scrollToCardByIndex(touchStartIndex, 'smooth'); return; }
      scrollToCardByIndex(deltaX < 0 ? touchStartIndex + 1 : touchStartIndex - 1);
    };

    templateRail.addEventListener('touchstart', handleTouchStart, { passive: true });
    templateRail.addEventListener('touchmove', handleTouchMove, { passive: true });
    templateRail.addEventListener('touchend', handleTouchEnd, { passive: true });
    templateRail.addEventListener('touchcancel', handleTouchEnd, { passive: true });
    templateRail.addEventListener('scroll', handleRailScroll, { passive: true });

    if (isMobileRail()) scheduleRailSelectionSync();
  }

  async function createQuestionRequest() {
    const formData = new FormData(createQuestionForm);
    const questionValue = String(formData.get('question_text') || '').trim();
    if (!questionValue) throw new Error('Type your question in the custom card');

    const selectedAvatarKey = selectedIsCustom ? 'custom' : (templateKey?.value || '');
    const selectedAvatarDataUrl = avatarDataByKey[selectedAvatarKey] || '';

    const data = await apiRequest(`${window.APP_CONFIG.baseUrl}/api/questions`, {
      method: 'POST',
      body: JSON.stringify({
        question_text: questionValue,
        template_key: formData.get('template_key') || '',
        avatar_data_url: selectedAvatarDataUrl,
      }),
    });

    generatedForCurrentTemplate = { signature: templateSignature(), data };
    if (presetPublicLinkText) presetPublicLinkText.textContent = data.public_url;
    return data;
  }

  async function syncCanvasImageToServer(data) {
    if (generatedForCurrentTemplate?.uploadedStoryImage) return;
    try {
      const blob = await buildTemplateShareBlob(data);
      if (!blob) return;
      const uploadForm = new FormData();
      uploadForm.append('image', blob, 'story.png');
      uploadForm.append('question_id', data.question_id);
      const res = await fetch(`${window.APP_CONFIG.baseUrl}/api/questions/update-story-image`, {
        method: 'POST',
        body: uploadForm,
      });
      const result = await res.json();
      if (result.ok && result.data?.story_image_url) {
        data.story_image_url = result.data.story_image_url;
        if (generatedForCurrentTemplate) {
          generatedForCurrentTemplate.data.story_image_url = result.data.story_image_url;
          generatedForCurrentTemplate.uploadedStoryImage = true;
        }
      }
    } catch {
      // Non-critical
    }
  }

  async function syncSpecificStoryImageToServer(data, blob, filename = 'story.png') {
    if (!blob) return '';
    const uploadForm = new FormData();
    uploadForm.append('image', blob, filename);
    uploadForm.append('question_id', data.question_id);
    const res = await fetch(`${window.APP_CONFIG.baseUrl}/api/questions/update-story-image`, {
      method: 'POST',
      body: uploadForm,
    });
    const result = await res.json().catch(() => ({ ok: false, message: 'Invalid server response' }));
    if (!result.ok || !result.data?.story_image_url) {
      throw new Error(result.message || 'Unable to upload story image');
    }
    data.story_image_url = result.data.story_image_url;
    if (generatedForCurrentTemplate) {
      generatedForCurrentTemplate.data.story_image_url = result.data.story_image_url;
    }
    return result.data.story_image_url;
  }

  createQuestionForm.addEventListener('submit', async (event) => {
    event.preventDefault();
    try {
      await createQuestionRequest();
      localStorage.removeItem(ONBOARDING_AVATAR_STORAGE_KEY);
      showToast('Story question generated');
    } catch (error) {
      showToast(error.message, true);
    }
  });

  async function ensureQuestionGenerated() {
    const signature = templateSignature();
    if (generatedForCurrentTemplate && generatedForCurrentTemplate.signature === signature) {
      return generatedForCurrentTemplate.data;
    }
    return createQuestionRequest();
  }

  async function triggerWelcomeBotOnce(questionId) {
    const normalizedQuestionId = Number(questionId || 0);
    if (normalizedQuestionId <= 0) return;
    if (welcomeBotTriggeredQuestionIds.has(normalizedQuestionId)) return;
    if (welcomeBotPendingQuestionIds.has(normalizedQuestionId)) return;
    welcomeBotPendingQuestionIds.add(normalizedQuestionId);
    try {
      const data = await apiRequest(`${window.APP_CONFIG.baseUrl}/api/bot/welcome-trigger`, {
        method: 'POST',
        body: JSON.stringify({ question_id: normalizedQuestionId }),
      });
      welcomeBotTriggeredQuestionIds.add(normalizedQuestionId);
      if (data?.sent === true) showToast('Pingo sent a welcome message');
    } catch {
      // Silent — must not interrupt copy/share flow
    } finally {
      welcomeBotPendingQuestionIds.delete(normalizedQuestionId);
    }
  }

  // FIX: share/copy buttons are guarded by actionInFlight to prevent duplicate API calls
  if (copyPresetLinkBtn) {
    copyPresetLinkBtn.addEventListener('click', async () => {
      if (actionInFlight) return;
      actionInFlight = true;
      try {
        const data = await ensureQuestionGenerated();
        await syncCanvasImageToServer(data);
        await copyToClipboard(data.public_url);
        showToast('Link copied');
        triggerWelcomeBotOnce(data.question_id);
      } catch (error) {
        showToast(error.message, true);
      } finally {
        actionInFlight = false;
      }
    });
  }

  if (sharePresetBtn) {
    sharePresetBtn.addEventListener('click', async () => {
      if (actionInFlight) return;
      actionInFlight = true;
      try {
        const data = await ensureQuestionGenerated();
        await syncCanvasImageToServer(data);
        if (shareGuideModal) {
          openShareGuide(data);
          return;
        }
        await shareCreateQuestionOutput(data);
        showToast('Share sheet opened');
        triggerWelcomeBotOnce(data.question_id);
      } catch (error) {
        showToast(error.message, true);
      } finally {
        actionInFlight = false;
      }
    });
  }

  shareGuideCloseEls.forEach((element) => {
    element.addEventListener('click', () => closeShareGuide());
  });

  sharePlatformTabs.forEach((tab) => {
    tab.addEventListener('click', () => {
      setSelectedSharePlatform(tab.dataset.platform || 'instagram');
    });
  });

  shareGuideSteps.forEach((stepEl, index) => {
    stepEl.style.cursor = 'pointer';
    stepEl.addEventListener('click', () => {
      if (selectedPlatform !== 'instagram') return;
      currentGuideStep = index;
      isInstagramGuide = true;
      renderPlatformGuide(selectedPlatform);
    });
  });

  if (shareGuideWhatsappBtn) {
    shareGuideWhatsappBtn.addEventListener('click', async () => {
      if (selectedPlatform === 'instagram') {
        if (currentGuideStep < instaSteps.length - 1) {
          currentGuideStep++;
          renderPlatformGuide(selectedPlatform);
          return;
        }

        if (!pendingShareGuideData) { closeShareGuide(); return; }
        const data = pendingShareGuideData;

        try {
          await shareInstagramStory(data);
        } catch (error) {
          showToast(error.message, true);
        }
        return;
      }

      if (!pendingShareGuideData) { closeShareGuide(); return; }
      const shareData = pendingShareGuideData;
      try {
        closeShareGuide();
       if (selectedPlatform === 'whatsapp') {
  try {
    // Call your native plugin for WhatsApp image sharing
    await WhatsAppShare.shareImage({
      imageUrl: shareData.image_url, // Ensure this is a valid file or URL
      caption: shareData.caption || '', // Optional caption
    });
    showToast('WhatsApp share opened');
    triggerWelcomeBotOnce(shareData.question_id);
  } catch (error) {
    showToast(error.message, true);
  }
} else {
  await shareCreateQuestionOutput(shareData, selectedPlatform);
  showToast('Share sheet opened');
  triggerWelcomeBotOnce(shareData.question_id);
}
      } catch (error) {
        showToast(error.message, true);
      }
    });
  }

  document.addEventListener('keydown', (event) => {
    if (event.key === 'Escape' && shareGuideModal && !shareGuideModal.classList.contains('hidden')) {
      closeShareGuide();
    }
  });

  function getSelectedTemplateButton() {
    return Array.from(templateButtons).find((button) => button.classList.contains('is-selected')) || null;
  }

function showDownloadModal(blob, publicUrl) {
  const url = URL.createObjectURL(blob);
  const modalDiv = document.createElement('div');
  modalDiv.innerHTML = `
    <div style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.5);z-index:9999;display:flex;align-items:center;justify-content:center;">
      <div style="background:white;border-radius:16px;padding:24px;max-width:400px;width:100%;text-align:center;">
        <h3 style="margin-bottom:16px;">Download Story Image</h3>
        <img src="${url}" style="max-width:100%;border-radius:12px;margin-bottom:16px;" alt="Instagram Story"/>
        <a href="${url}" download="instagram-story.png" style="display:block;margin-bottom:16px;" class="btn-primary">⬇️ Download Image</a>
        <p style="font-size:13px;color:#555;margin-bottom:16px;">
          After downloading, open Instagram → Your Story → Add Image.<br>
          Tap the LINK sticker and paste your link:<br>
          <span style="font-family:monospace;color:#6366f1;">${publicUrl}</span>
        </p>
        <button onclick="this.closest('div').parentNode.remove()" style="margin-top:8px;">Close</button>
      </div>
    </div>
  `;
  document.body.appendChild(modalDiv);
}

  async function buildTemplateShareBlob(data, options = {}) {
  const { forInstagram = false } = options;
    const selectedButton = getSelectedTemplateButton();
    if (!selectedButton) return null;

    const selectedKey = selectedButton.dataset.custom === '1'
      ? 'custom'
      : (templateKey?.value || selectedButton.dataset.key || '');
    const uploadedAvatar = avatarDataByKey[selectedKey] || '';
    const slotImage = selectedButton.querySelector('[data-avatar-image]');
    const fallbackAvatar = slotImage?.getAttribute('src') || '';
    const avatarSrc = uploadedAvatar || fallbackAvatar;
    if (!avatarSrc) return null;

    const canvas = document.createElement('canvas');
    canvas.width = 1080;
    canvas.height = 1920;
    const ctx = canvas.getContext('2d');
    if (!ctx) return null;

    const cardX = 120, cardY = 455, cardW = 840, cardH = 585;
    const topH = 300, cardRadius = 56, avatarSize = 156;
    const avatarX = (canvas.width - avatarSize) / 2;
    const avatarY = cardY - avatarSize / 2;

    let avatarImage;
    try {
      avatarImage = await loadImageFromSource(avatarSrc);
    } catch {
      return null;
    }

    ctx.save();
    ctx.filter = 'blur(26px)';
    drawCoverImage(ctx, avatarImage, -80, -80, canvas.width + 160, canvas.height + 160);
    ctx.restore();

    const bgOverlay = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
    bgOverlay.addColorStop(0, 'rgba(22, 24, 34, 0.20)');
    bgOverlay.addColorStop(1, 'rgba(22, 24, 34, 0.34)');
    ctx.fillStyle = bgOverlay;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    drawRoundedRectPath(ctx, cardX, cardY, cardW, cardH, cardRadius);
    ctx.fillStyle = '#ffffff';
    ctx.fill();

    ctx.save();
    drawRoundedRectPath(ctx, cardX, cardY, cardW, cardH, cardRadius);
    ctx.clip();
    const start = getComputedStyle(selectedButton).getPropertyValue('--card-start').trim() || '#30b4ff';
    const end = getComputedStyle(selectedButton).getPropertyValue('--card-end').trim() || '#5b4eff';
    const topGradient = ctx.createLinearGradient(cardX, cardY, cardX + cardW, cardY + topH);
    topGradient.addColorStop(0, start);
    topGradient.addColorStop(1, end);
    ctx.fillStyle = topGradient;
    ctx.fillRect(cardX, cardY, cardW, topH);
    ctx.restore();

    const avatarRing = ctx.createLinearGradient(avatarX, avatarY, avatarX + avatarSize, avatarY + avatarSize);
    avatarRing.addColorStop(0, '#49b6ff');
    avatarRing.addColorStop(1, '#5961ff');
    ctx.beginPath();
    ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
    ctx.fillStyle = avatarRing;
    ctx.fill();

    ctx.save();
    ctx.beginPath();
    ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 - 6, 0, Math.PI * 2);
    ctx.closePath();
    ctx.clip();
    drawCoverImage(ctx, avatarImage, avatarX + 6, avatarY + 6, avatarSize - 12, avatarSize - 12);
    ctx.restore();

    const rawTitle = selectedButton.querySelector('.template-card-title')?.textContent || '';
    const title = String(rawTitle).trim() || 'Confessions';
    const rawQuestion = (data?.question_text || questionText?.value || selectedTemplateValue || selectedButton.dataset.value || '').trim();
    const bodyText = rawQuestion || 'send me anonymous confessions';

    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.font = '900 78px "Plus Jakarta Sans", "Arial Black", sans-serif';
    ctx.lineJoin = 'round';
    ctx.lineWidth = 12;
    ctx.strokeStyle = 'rgba(15, 23, 42, 0.38)';
    ctx.strokeText(title, canvas.width / 2, cardY + 195);
    ctx.lineWidth = 7;
    ctx.strokeStyle = '#ffffff';
    ctx.strokeText(title, canvas.width / 2, cardY + 195);
    ctx.fillStyle = '#f8fbff';
    ctx.fillText(title, canvas.width / 2, cardY + 195);

    ctx.fillStyle = '#0b0f1a';
    ctx.font = '700 68px "Plus Jakarta Sans", "Inter", sans-serif';
    const maxWidth = cardW - 110;
    const words = bodyText.split(/\s+/).filter(Boolean);
    const lines = [];
    let current = '';
    words.forEach((word) => {
      const test = current ? `${current} ${word}` : word;
      if (ctx.measureText(test).width <= maxWidth || current === '') {
        current = test;
      } else {
        lines.push(current);
        current = word;
      }
    });
    if (current) lines.push(current);
    const visibleLines = lines.slice(0, 2);
    const lineHeight = 78;
    const textStartY = cardY + topH + 120;
    visibleLines.forEach((line, index) => ctx.fillText(line, canvas.width / 2, textStartY + index * lineHeight));

    ctx.strokeStyle = 'rgba(255,255,255,0.92)';
    ctx.lineWidth = 15;
    ctx.lineCap = 'round';
    const arrowY = 1560;
    const drawArrow = (cx) => {
      ctx.beginPath();
      ctx.moveTo(cx, arrowY - 70);
      ctx.lineTo(cx, arrowY + 38);
      ctx.stroke();
      ctx.beginPath();
      ctx.moveTo(cx - 38, arrowY + 6);
      ctx.lineTo(cx, arrowY + 48);
      ctx.lineTo(cx + 38, arrowY + 6);
      ctx.stroke();
    };
if (!forInstagram) {
  drawArrow(330);
  drawArrow(540);
  drawArrow(750);
}

    return new Promise((resolve) => canvas.toBlob((blob) => resolve(blob || null), 'image/png', 0.98));
  }

async function buildInstagramStoryBlob(data) {
  // 1. Get SAME base template (WhatsApp design)
  const baseBlob = await buildTemplateShareBlob(data, { forInstagram: true });
  if (!baseBlob) return null;

  const baseImg = await loadImageFromSource(URL.createObjectURL(baseBlob));

  const canvas = document.createElement('canvas');
  canvas.width = 1080;
  canvas.height = 1920;
  const ctx = canvas.getContext('2d');

  // Draw base image
  ctx.drawImage(baseImg, 0, 0, canvas.width, canvas.height);

  // ================= OVERLAY =================

  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';

  // 🔵 LINK BOX
  const boxWidth = 520;
  const boxHeight = 120;
  const boxX = (canvas.width - boxWidth) / 2;
  const boxY = 1150;

  ctx.fillStyle = '#ffffff';
  drawRoundedRectPath(ctx, boxX, boxY, boxWidth, boxHeight, 30);
  ctx.fill();

  // Text
  ctx.fillStyle = '#000';
  ctx.font = 'bold 36px Arial';
  ctx.fillText('Paste your PingXO.com', canvas.width / 2, boxY + 40);

  ctx.fillStyle = '#007AFF';
  ctx.font = 'bold 44px Arial';
  ctx.fillText('link here', canvas.width / 2, boxY + 85);

  // 🔵 ARROWS
  ctx.strokeStyle = '#ffffff';
  ctx.lineWidth = 12;
  ctx.lineCap = 'round';

const drawPremiumArrowUp = (x, y, i = 0) => {
  // slight stagger for visual "motion feel"
  const offset = (i % 2 === 0) ? -6 : 6;

  ctx.save();

  // Glow
  ctx.shadowColor = 'rgba(255,255,255,0.9)';
  ctx.shadowBlur = 25;

  // Main line
  ctx.strokeStyle = '#ffffff';
  ctx.lineWidth = 14;
  ctx.lineCap = 'round';

  ctx.beginPath();
  ctx.moveTo(x, y + offset);
  ctx.lineTo(x, y - 85 + offset);
  ctx.stroke();

  // Arrow head (rounded + bold)
  ctx.beginPath();
  ctx.moveTo(x - 26, y - 60 + offset);
  ctx.lineTo(x, y - 95 + offset);
  ctx.lineTo(x + 26, y - 60 + offset);
  ctx.stroke();

  ctx.restore();
};

drawPremiumArrowUp(330, 1350, 0);
drawPremiumArrowUp(540, 1350, 1);
drawPremiumArrowUp(750, 1350, 2);

  return new Promise((resolve) =>
    canvas.toBlob((blob) => resolve(blob), 'image/png', 1)
  );
}

  async function shareCreateQuestionOutput(data, platform = 'whatsapp') {
    const composedBlob = await buildTemplateShareBlob(data);
    if (composedBlob) {
      await shareBlobOrLink(composedBlob, data.public_url, platform);
      return;
    }
    await shareImageOrLink(data.story_image_url, data.public_url, platform);
  }
}

// ================= MESSAGE FORM =================
const messageForm = document.getElementById('messageForm');
if (messageForm) {
  if (window.APP_CONFIG.requireCaptcha) loadCaptchaQuestion();

  messageForm.addEventListener('submit', async (event) => {
    event.preventDefault();
    const formData = new FormData(messageForm);
    try {
      await apiRequest(`${window.APP_CONFIG.baseUrl}/api/messages`, {
        method: 'POST',
        body: JSON.stringify({
          question_id: Number(formData.get('question_id')),
          message: formData.get('message'),
          captcha_answer: formData.get('captcha_answer') || '',
        }),
      });
      messageForm.reset();
      showToast('Reply sent anonymously');
      if (window.APP_CONFIG.requireCaptcha) loadCaptchaQuestion();
    } catch (error) {
      showToast(error.message, true);
      if (window.APP_CONFIG.requireCaptcha) loadCaptchaQuestion();
    }
  });
}

// ================= SHARE BUTTONS =================
document.querySelectorAll('[data-share-image]').forEach((button) => {
  button.addEventListener('click', async () => {
    const imageUrl = button.getAttribute('data-share-image');
    if (!imageUrl) return;
    await shareImageOrLink(imageUrl, imageUrl);
  });
});

async function loadCaptchaQuestion() {
  const target = document.getElementById('captchaQuestion');
  if (!target) return;
  try {
    const data = await apiRequest(`${window.APP_CONFIG.baseUrl}/api/captcha`);
    target.textContent = data.question;
  } catch {
    target.textContent = 'Captcha unavailable';
  }
}

async function shareImageOrLink(imageUrl, fallbackUrl, platform = 'whatsapp') {
  try {
    const response = await fetch(imageUrl, { cache: 'no-store' });
    const blob = await response.blob();
    const file = new File([blob], 'story.png', { type: blob.type || 'image/png' });
    if (navigator.canSh