'use strict';
const $ = selector => document.querySelector(selector);
const form = $('#transcript-form');
const input = $('#reel-url');
let result = '';
let active = false;
let resultSource = '';
function normalizeLink(value) {
let url;
try { url = new URL(value.trim()); } catch { return null; }
if (url.protocol !== 'https:' || !['instagram.com', 'www.instagram.com', 'm.instagram.com'].includes(url.hostname.toLowerCase()) || url.port || url.username || url.password) return null;
const match = url.pathname.match(/^\/(?:reel|reels|p|tv)\/([A-Za-z0-9_-]+)\/?$/);
const kind = url.pathname.startsWith('/p/') ? 'p' : url.pathname.startsWith('/tv/') ? 'tv' : 'reel';
return match ? `https://www.instagram.com/${kind}/${match[1]}/` : null;
}
function showError(message) {
$('#input-error').textContent = message;
$('#input-error').hidden = !message;
$('.url-field').classList.toggle('invalid', Boolean(message));
input.setAttribute('aria-invalid', message ? 'true' : 'false');
}
function status(message, state = '') {
$('#status').textContent = message;
$('#status').className = `status ${state}`;
}
function clearResult() {
result = ''; resultSource = '';
$('#transcript').value = '';
$('#transcript').hidden = true;
$('#empty-state').hidden = false;
$('#copy').disabled = true;
$('#download').disabled = true;
$('#word-count').textContent = 'Nothing to copy yet';
$('#result-meta').textContent = 'YOUR TRANSCRIPT';
}
function setBusy(busy) {
active = busy;
input.disabled = busy;
$('#generate').disabled = busy;
$('#clear-url').disabled = busy;
$('#generate-label').textContent = busy ? 'Working on your Reel…' : 'Get transcript';
$('.spinner').hidden = !busy;
$('#button-arrow').hidden = busy;
$('#result-panel').setAttribute('aria-busy', String(busy));
}
input.addEventListener('input', () => {
showError('');
$('#clear-url').hidden = !input.value;
if (resultSource && normalizeLink(input.value) !== resultSource) { clearResult(); status('Ready for another link.'); }
});
input.addEventListener('blur', () => { if (input.value.trim() && !normalizeLink(input.value)) showError('Use a full public Instagram Reel, video-post or IGTV link.'); });
$('#clear-url').addEventListener('click', () => { input.value = ''; $('#clear-url').hidden = true; showError(''); clearResult(); status('Ready when you are.'); input.focus(); });
form.addEventListener('submit', async event => {
event.preventDefault();
if (active) return;
const url = normalizeLink(input.value);
if (!url) { showError('Paste a full Instagram Reel, video-post or IGTV link to continue.'); input.focus(); return; }
showError(''); clearResult(); setBusy(true);
const requestStarted = performance.now();
form.dataset.requestStartedUtc = new Date().toISOString();
delete form.dataset.elapsedMilliseconds;
delete form.dataset.completedUtc;
status('Checking the public video and transcribing in the cloud. Keep this tab open.', 'working');
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 600000);
try {
const response = await fetch('/api/transcribe', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({url}), signal: controller.signal });
const data = await response.json();
if (!response.ok) throw new Error(data.error || 'The request could not be completed.');
if (!data.text || !data.text.trim()) throw new Error('No spoken text was detected. No transcript was created.');
result = data.text.trim(); resultSource = url;
$('#transcript').value = result;
$('#transcript').hidden = false; $('#empty-state').hidden = true;
$('#copy').disabled = false; $('#download').disabled = false;
if (Number.isInteger(data.word_count_whitespace)) {
$('#word-count').textContent = `${data.word_count_whitespace} ${data.word_count_whitespace === 1 ? 'word' : 'words'}`;
} else if (typeof Intl.Segmenter === 'function') {
const count = [...new Intl.Segmenter(undefined, {granularity:'word'}).segment(result)].filter(part => part.isWordLike).length;
$('#word-count').textContent = `${count} ${count === 1 ? 'word' : 'words'}`;
} else $('#word-count').textContent = 'Ready to copy';
$('#result-meta').textContent = data.language_name || data.language || 'TRANSCRIPT';
status('Your transcript is ready. Copy it or download your text file.');
} catch (error) {
status(error.name === 'AbortError' ? 'This request timed out. No transcript was created.' : error.message === 'Failed to fetch' ? 'The cloud service could not be reached. No transcript was created.' : error.message, 'failed');
} finally {
clearTimeout(timer); setBusy(false);
form.dataset.elapsedMilliseconds = String(performance.now() - requestStarted);
form.dataset.completedUtc = new Date().toISOString();
}
});
$('#copy').addEventListener('click', async () => {
if (!result) return;
try { await navigator.clipboard.writeText(result); $('#copy').innerHTML = '✓ Copied'; status('Copied to your clipboard.'); setTimeout(() => { $('#copy').innerHTML = '⧉ Copy text'; }, 1800); }
catch { $('#transcript').focus(); $('#transcript').select(); status('Clipboard access was blocked. The text is selected; use your device’s Copy command.'); }
});
$('#download').addEventListener('click', () => {
if (!result) return;
const blob = new Blob([result + '\n'], {type:'text/plain;charset=utf-8'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a'); a.href = url; a.download = 'instagram-transcript.txt'; a.click();
setTimeout(() => URL.revokeObjectURL(url), 2000);
status('Text file prepared for download. Check your browser’s downloads.');
});
fetch('/api/status').then(r => r.json()).then(data => {
if (!data.ready) status(data.message || 'Cloud processing is temporarily unavailable. No transcript was created.', 'failed');
}).catch(() => status('Cloud processing could not be reached. No transcript was created.', 'failed'));