PageSourceSearch

https://transcript.you/tube

html transcript.you collected 2026-09-24 10:33:30 UTC 161,203 bytes, 3,180 lines download raw bytes

1<!-- new -->
2<!DOCTYPE html>
3<html lang="en" data-theme="light">
4
5<head>
6    <meta charset="UTF-8" />
7    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
8    <meta name="viewport" content="width=device-width, initial-scale=1.0" /><link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin />
9    <link rel="preconnect" href="https://cdn.tailwindcss.com" crossorigin />
10    <link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin />
11    <link rel="preconnect" href="https://js.stripe.com" crossorigin />
12    <link rel="dns-prefetch" href="https://m.stripe.network" />
13    <link rel="dns-prefetch" href="https://b.stripecdn.com" /><title>Get any Youtube Transcript | Transcript.you</title>
14    
14<script async data-id="101459384" src="//static.getclicky.com/js"></script>
14
15
16    <!-- Google tag (gtag.js) -->
vendor: 68 bytes, lines 16-17
16
17    <script async src="https://www.googletagmanager.com/gtag/js?id=
17AW-634605019
vendor: 16 bytes, lines 17-18
17"></script>
18    
18<script>
19        
vendor: 155 bytes, lines 19-23
19window.dataLayer = window.dataLayer || [];
20        function gtag() { dataLayer.push(arguments); }
21        gtag('js', new Date());
22
23        gtag('config', '
23AW-634605019
vendor: 8 bytes, lines 23-24
23');
24    
24</script>
24
25
26    
26<script>
27        // ================================================================
28        // Universal upgrade-modal click tracker + iOS-Safari-safe body lock.
29        // Each viewer template defines its own window.showUpgradeModal;
30        // we trap the property setter so EVERY assignment is wrapped and
31        // EVERY call fires a tracking event with a location label.
32        // ================================================================
33        (function () {
34            window.__trackUpgradeOpen = function (location, extraMeta) {
35                var loc = location || window.location.pathname || 'unknown';
36                var meta = Object.assign({location: loc}, extraMeta || {});
37                try {
38                    fetch('/api/track', {
39                        method: 'POST',
40                        headers: {'Content-Type': 'application/json'},
41                        body: JSON.stringify({event: 'upgrade_modal_open', meta: meta})
42                    });
43                } catch (e) {}
44                if (window.gtag) {
45                    gtag('event', 'upgrade_modal_open', meta);
46                }
47            };
48
49            var _impl = null;
50            Object.defineProperty(window, 'showUpgradeModal', {
51                configurable: true,
52                get: function () { return _impl; },
53                set: function (fn) {
54                    _impl = function () {
55                        var args = Array.prototype.slice.call(arguments);
56                        var loc = (typeof args[0] === 'string') ? args[0] : null;
57                        try { window.__trackUpgradeOpen(loc); } catch (e) {}
58                        return fn.apply(this, args);
59                    };
60                }
61            });
62
63            document.addEventListener('click', function (e) {
64                var t = e.target && e.target.closest && e.target.closest('[data-upgrade-cta]');
65                if (!t) return;
66                try { window.__trackUpgradeOpen(t.getAttribute('data-upgrade-cta')); } catch (_) {}
67            }, true);
68
69            // ============================================================
70            // iOS-Safari-safe body lock for any open <dialog> on the page.
71            //
72            // The CSS :has() / overscroll-behavior trick fails on iOS Safari
73            // with embedded iframes — touches inside Stripe's iframe still
74            // scroll the page underneath. The bulletproof workaround:
75            // when a dialog opens, save scrollY and pin body with
76            // position:fixed; top:-scrollY; width:100%. On close, restore.
77            // Watches every <dialog> via a single MutationObserver so
78            // nothing has to opt in.
79            // ============================================================
80            window.__lockBodyScroll = function () {
81                if (document.body.dataset.lockedScrollY) return; // already locked
82                var y = window.scrollY || window.pageYOffset || 0;
83                document.body.dataset.lockedScrollY = String(y);
84                document.body.style.position = 'fixed';
85                document.body.style.top = '-' + y + 'px';
86                document.body.style.left = '0';
87                document.body.style.right = '0';
88                document.body.style.width = '100%';
89            };
90            window.__unlockBodyScroll = function () {
91                var y = document.body.dataset.lockedScrollY;
92                if (!y) return;
93                document.body.style.position = '';
94                document.body.style.top = '';
95                document.body.style.left = '';
96                document.body.style.right = '';
97                document.body.style.width = '';
98                delete document.body.dataset.lockedScrollY;
99                window.scrollTo(0, parseInt(y, 10) || 0);
100            };
101
102            function anyDialogOpen() {
103                return !!document.querySelector('dialog[open]');
104            }
105            function reconcileBodyLock() {
106                if (anyDialogOpen()) window.__lockBodyScroll();
107                else window.__unlockBodyScroll();
108            }
109
110            // Watch every dialog's `open` attribute so we react to .showModal(),
111            // .show(), .close(), and ::backdrop dismiss equally.
112            function watchDialogs() {
113                document.querySelectorAll('dialog').forEach(function (d) {
114                    if (d.dataset._lockWatched) return;
115                    d.dataset._lockWatched = '1';
116                    new MutationObserver(reconcileBodyLock)
117                        .observe(d, {attributes: true, attributeFilter: ['open']});
118                    d.addEventListener('close', reconcileBodyLock);
119                });
120            }
121            if (document.readyState === 'loading') {
122                document.addEventListener('DOMContentLoaded', watchDialogs);
123            } else {
124                watchDialogs();
125            }
126            // New dialogs added later (some templates inject them).
127            new MutationObserver(function () { watchDialogs(); reconcileBodyLock(); })
128                .observe(document.documentElement, {childList: true, subtree: true});
129        })();
130    </script>
130
131
132    
132<script async type="text/javascript"
133        src="https://s3.us-east-2.amazonaws.com/telemetry.bydata/transcript.you/bydataAnalytics.js?cId=asc202860"></script>
133
134
135
136<link rel="canonical"
137    href="https://transcript.you/" />
138
139
140    
141
142
143
144
145
146
147    
148
149<meta name="robots" content="index, follow">
150
151<meta name="title"
152    content="Get a YouTube Transcript - Transcript.you" />
153
154
155<meta name="description"
156    content="Easily get the full transcript of any YouTube video for free. Our tool allows you to convert YouTube videos to text, with clickable timestamps and multi-language support." />
157
158
159
160    
161    <!-- OpenGraph -->
162    <meta property="og:type" content="website" />
163    <meta property="og:site_name" content="Transcript.you" />
164    <meta property="og:title" content="Get any Youtube Transcript | Transcript.you" />
165    <meta property="og:description" content="Get the transcript for any YouTube video quickly and easily with our free tool. Supports multiple languages and clickable timestamps." />
166    <meta property="og:url" content="https://transcript.you/tube" />
167    <meta property="og:image" content="https://transcript.you/static/og/default.png" />
168    <meta property="og:image:width" content="1200" />
169    <meta property="og:image:height" content="630" />
170
171    <!-- Twitter Card -->
172    <meta name="twitter:card" content="summary_large_image" />
173    <meta name="twitter:site" content="@transcriptyou" />
174    <meta name="twitter:title" content="Get any Youtube Transcript | Transcript.you" />
175    <meta name="twitter:description" content="Get the transcript for any YouTube video quickly and easily with our free tool. Supports multiple languages and clickable timestamps." />
176    <meta name="twitter:image" content="https://transcript.you/static/og/default.png" />
177    
178
179
180    
180<script type="application/ld+json">
181  {
182    "@context": "https://schema.org",
183    "@graph": [
184      {
185        "@type": "Organization",
186        "name": "Transcript.you",
187        "url": "https://transcript.you",
188        "logo": "https://transcript.you/static/apple-touch-icon-180x180.png",
189        "description": "AI transcription platform for YouTube, TikTok, podcasts, lectures, and any audio or video file. 95%+ accuracy across 50+ languages.",
190        "knowsAbout": [
191          "AI transcription",
192          "automatic speech recognition",
193          "ASR",
194          "OpenAI Whisper",
195          "audio transcription",
196          "video transcription",
197          "speech-to-text",
198          "voice-to-text",
199          "subtitle generation",
200          "SubRip SRT",
201          "WebVTT",
202          "speaker diarization",
203          "word error rate",
204          "podcast transcription",
205          "lecture transcription",
206          "meeting transcription",
207          "interview transcription",
208          "legal transcription",
209          "medical transcription",
210          "Zoom transcription",
211          "Google Meet transcription",
212          "Microsoft Teams transcription",
213          "MP3 transcription",
214          "WAV transcription",
215          "M4A transcription",
216          "MP4 video transcription",
217          "audio captioning",
218          "video captioning",
219          "closed captions",
220          "accessibility",
221          "AI summarization",
222          "blog post generation",
223          "show notes",
224          "translation"
225        ],
226        "sameAs": []
227        
228      },
229      {
230        "@type": "WebSite",
231        "name": "Transcript.you",
232        "url": "https://transcript.you",
233        "potentialAction": {
234          "@type": "SearchAction",
235          "target": "https://transcript.you/tube?v={search_term_string}",
236          "query-input": "required name=search_term_string"
237        }
238      }
239    ]
240  }
241  </script>
241
242
243    <!-- Favicon -->
244    <!-- Favicon -->
245    <link rel="apple-touch-icon" sizes="57x57" href="/static/apple-touch-icon-57x57.png">
246    <link rel="apple-touch-icon" sizes="60x60" href="/static/apple-touch-icon-60x60.png">
247    <link rel="apple-touch-icon" sizes="72x72" href="/static/apple-touch-icon-72x72.png">
248    <link rel="apple-touch-icon" sizes="76x76" href="/static/apple-touch-icon-76x76.png">
249    <link rel="apple-touch-icon" sizes="114x114"
250        href="/static/apple-touch-icon-114x114.png">
251    <link rel="apple-touch-icon" sizes="120x120"
252        href="/static/apple-touch-icon-120x120.png">
253    <link rel="apple-touch-icon" sizes="144x144"
254        href="/static/apple-touch-icon-144x144.png">
255    <link rel="apple-touch-icon" sizes="152x152"
256        href="/static/apple-touch-icon-152x152.png">
257    <link rel="apple-touch-icon" sizes="167x167"
258        href="/static/apple-touch-icon-167x167.png">
259    <link rel="apple-touch-icon" sizes="180x180"
260        href="/static/apple-touch-icon-180x180.png">
261    <link rel="apple-touch-icon" sizes="1024x1024"
262        href="/static/apple-touch-icon-1024x1024.png">
263    <link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png">
264    <link rel="icon" type="image/png" sizes="32x32" href="/static/favicon-32x32.png">
265    <link rel="icon" type="image/png" sizes="48x48" href="/static/favicon-48x48.png">
266    <link rel="icon" type="image/png" sizes="64x64" href="/static/favicon-64x64.png">
267    <link rel="icon" type="image/png" sizes="128x128" href="/static/favicon-128x128.png">
268    <link rel="icon" type="image/png" sizes="256x256" href="/static/favicon-256x256.png">
269    <link rel="manifest" href="/static/site.webmanifest">
270    <meta name="msapplication-TileImage" content="/static/mstile-144x144.png">
271
272    <!-- Phosphor Icons CDN -->
273    <link rel="stylesheet" type="text/css"
274        href="https://cdn.jsdelivr.net/npm/@phosphor-icons/[email protected]/src/duotone/style.css" />
275
276    <!-- DaisyUI and Tailwind CSS -->
277    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/full.min.css" rel="stylesheet" type="text/css" />
278    
278<script src="https://cdn.tailwindcss.com?plugins=typography"></script>
278
279
280    <!-- Custom Fonts (Font Awesome for old icons) -->
281    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
282
283<!-- Matomo -->
284<script>
285  var _paq = window._paq = window._paq || [];
286  /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
287  _paq.push(['trackPageView']);
288  _paq.push(['enableLinkTracking']);
289  (function() {
290    var u="//tracking.transcript.you/";
291    _paq.push(['setTrackerUrl', u+'matomo.php']);
292    _paq.push(['setSiteId', '']);
293    var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
294    g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
295  })();
296</script>
296
297<!-- End Matomo Code -->
298</head>
299
300<body class="bg-gray-50" data-authenticated="false">
301    <div class="drawer">
302        <input id="my-drawer" type="checkbox" class="drawer-toggle" />
303        <div class="drawer-content flex flex-col min-h-screen">
304            <!-- Page content here -->
305
306            <!-- Desktop Bookmark Bar (hidden on /premium so pricing has full focus) -->
307            
308            <section class="hidden sm:flex justify-center w-full px-4 py-2 text-center bg-black text-white">
309                <div class="block mx-auto text-xs font-semibold max-w-fit">
310                    <svg xmlns="http://www.w3.org/2000/svg" class="inline w-5 h-5 fill-current" viewBox="0 0 256 256">
311                        <path
312                            d="M208,144a80,80,0,0,1-160,0c0-30.57,14.42-58.26,31-80l33,32,26.27-72C159.86,41.92,208,88.15,208,144Z"
313                            opacity="0.2"></path>
314                        <path
315                            d="M183.89,153.34a57.6,57.6,0,0,1-46.56,46.55A8.75,8.75,0,0,1,136,200a8,8,0,0,1-1.32-15.89c16.57-2.79,30.63-16.85,33.44-33.45a8,8,0,0,1,15.78,2.68ZM216,144a88,88,0,0,1-176,0c0-27.92,11-56.47,32.66-84.85a8,8,0,0,1,11.93-.89l24.12,23.41,22-60.41a8,8,0,0,1,12.63-3.41C165.21,36,216,84.55,216,144Zm-16,0c0-46.09-35.79-85.92-58.21-106.33L119.52,98.74a8,8,0,0,1-13.09,3L80.06,76.16C64.09,99.21,56,122,56,144a72,72,0,0,0,144,0Z">
316                        </path>
317                    </svg>
318                    Finding this tool useful? Bookmark us
319                    <span class="hidden macos">
320                        <kbd class="kbd text-base-content">cmd</kbd>+<kbd class="kbd text-base-content">d</kbd>
321                    </span>
322                    <span class="hidden window">
323                        <kbd class="kbd text-base-content">ctrl</kbd>+<kbd class="kbd text-base-content">d</kbd>
324                    </span>
325                    for easy and fast access!
326                </div>
327            </section>
328            
329
330            <!-- Mobile Bookmark Alert (hidden on /premium) -->
331            
332            <div id="mobile-bookmark-alert"
333                class="flex items-center justify-between w-full px-4 py-2 bg-black text-white text-xs sm:hidden">
334                <div class="flex items-center">
335                    <i class="ph-duotone ph-star text-lg mr-2"></i>
336                    <span class="font-semibold">Remember to bookmark us!</span>
337                </div>
338                <button class="btn btn-xs btn-ghost" onclick="this.parentElement.style.display='none'">✕</button>
339            </div>
340            
341
342            <nav class="navbar bg-base-100 border-b border-base-200 w-full max-w-full px-4 lg:px-8 relative z-50">
343                <!-- navbar-start: flex-grow to take up available space -->
344                <div class="navbar-start flex-grow">
345                    <!-- Hamburger Menu Button -->
346                    <label for="my-drawer" class="btn btn-ghost lg:hidden drawer-button"
347                        style="padding-left:0rem !important; padding-right:0rem !important; ">
348                        <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
349                            stroke="currentColor">
350                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
351                                d="M4 6h16M4 12h8m-8 6h16" />
352                        </svg>
353                    </label>
354                    <!-- Logo -->
355                    <a class="flex items-center text-xl normal-case" href="/">
356                        <svg xmlns="http://www.w3.org/2000/svg" class="hidden h-8 w-8 fill-blue-500 sm:inline"
357                            viewBox="0 0 256 256">
358                            <path d="M232,64V192a8,8,0,0,1-8,8H32a8,8,0,0,1-8-8V64a8,8,0,0,1,8-8H224A8,8,0,0,1,232,64Z"
359                                opacity="0.2"></path>
360                            <path
361                                d="M224,48H32A16,16,0,0,0,16,64V192a16,16,0,0,0,16,16H224a16,16,0,0,0,16-16V64A16,16,0,0,0,224,48Zm0,144H32V64H224V192ZM48,136a8,8,0,0,1,8-8H72a8,8,0,0,1,0,16H56A8,8,0,0,1,48,136Zm160,0a8,8,0,0,1-8,8H104a8,8,0,0,1,0-16h96A8,8,0,0,1,208,136Zm-48,32a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16h96A8,8,0,0,1,160,168Zm48,0a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16h16A8,8,0,0,1,208,168Z">
362                            </path>
363                        </svg>
364                        <span class="ml-2 font-bold text-gray-700">Transcript.you</span>
365                    </a>
366
367                    <!-- PRIMARY FUNCTIONS (LEFT) -->
368
369                    <ul
370                        class="menu menu-horizontal px-1 hidden lg:flex ml-4 gap-x-1 flex-nowrap whitespace-nowrap items-center">
371                        <!-- YOUTUBE DROPDOWN -->
372                        <div class="dropdown dropdown-hover">
373                            <label tabindex="0" class="btn btn-ghost btn-sm gap-1 px-3">
374                                <i class="ph-duotone ph-youtube-logo text-red-600"></i>
375                                YouTube
376                                <i class="ph-duotone ph-caret-down text-xs"></i>
377                            </label>
378                            <ul tabindex="0"
379                                class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-64">
380                                <li><a href="/tube">YouTube Transcript Generator</a></li>
381                                <li><a href="/transcribe-youtube-videos">Transcribe
382                                        YouTube Videos</a></li>
383                                <li><a href="/shorts">Shorts Transcript</a></li>
384                                <li><a href="/video-to-text">Video to Text</a></li>
385                                <li><a href="/generate-subtitles">Generate
386                                        Subtitles</a></li>
387                                <li><a href="/video-to-srt">Video to SRT</a></li>
388                            </ul>
389                        </div>
390
391                        <!-- AUDIO DROPDOWN -->
392                        <div class="dropdown dropdown-hover">
393                            <label tabindex="0" class="btn btn-ghost btn-sm gap-1 px-3">
394                                <i class="ph-duotone ph-microphone-stage text-purple-600"></i>
395                                Audio
396                                <i class="ph-duotone ph-caret-down text-xs"></i>
397                            </label>
398                            <ul tabindex="0"
399                                class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-64">
400                                <li><a href="/transcribe-audio">Transcribe Audio File</a></li>
401                                <li><a href="/mp3-to-text">MP3 to Text</a></li>
402                                <li><a href="/voice-to-text">Voice to Text</a></li>
403                                <li><a href="/meeting-transcription">Meeting
404                                        Transcription</a></li>
405                                <li><a href="/interview-transcription">Interview
406                                        Transcription</a></li>
407                                <li><a href="/zoom-to-text">Zoom to Text</a></li>
408                            </ul>
409                        </div>
410
411                        <!-- BULK TRANSCRIPTS DROPDOWN -->
412                        <div class="dropdown dropdown-hover">
413                            <label tabindex="0" class="btn btn-ghost btn-sm gap-1 px-3">
414                                <i class="ph-duotone ph-upload-simple text-green-600"></i>
415                                Bulk
416                                <i class="ph-duotone ph-caret-down text-xs"></i>
417                            </label>
418                            <ul tabindex="0"
419                                class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-72">
420                                <li><a href="/bulk" class="font-bold"><i class="ph-duotone ph-stack text-indigo-600"></i> See all bulk sources</a></li>
421                                <div class="divider my-0"></div>
422                                <li><a href="/bulk-transcripts"><i class="ph-duotone ph-youtube-logo text-red-500"></i> Bulk YouTube Transcripts</a></li>
423                                <li><a href="/bulk-tiktok"><i class="ph-duotone ph-tiktok-logo text-pink-500"></i> Bulk TikTok Transcripts</a></li>
424                                <li><a href="/bulk-spotify"><i class="ph-duotone ph-spotify-logo text-green-500"></i> Bulk Spotify Podcasts</a></li>
425                                <li><a href="/bulk-vimeo"><i class="ph-duotone ph-video text-sky-500"></i> Bulk Vimeo</a></li>
426                                <li><a href="/bulk-twitch"><i class="ph-duotone ph-twitch-logo text-purple-600"></i> Bulk Twitch VODs</a></li>
427                                <li><a href="/bulk-audio"><i class="ph-duotone ph-microphone text-emerald-500"></i> Bulk Audio Upload</a></li>
428                                
429                            </ul>
430                        </div>
431
432                        <!-- TIKTOK DROPDOWN -->
433                        <div class="dropdown dropdown-hover">
434                            <label tabindex="0" class="btn btn-ghost btn-sm gap-1 px-3">
435                                <i class="ph-duotone ph-tiktok-logo text-slate-800"></i>
436                                TikTok
437                                <i class="ph-duotone ph-caret-down text-xs"></i>
438                            </label>
439                            <ul tabindex="0"
440                                class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-64">
441                                <li><a href="/tiktok" class="font-bold"><i class="ph-duotone ph-text-aa text-cyan-600"></i> TikTok Transcript</a></li>
442                                <li><a href="/bulk-tiktok"><i class="ph-duotone ph-stack text-indigo-600"></i> Bulk TikTok Transcripts</a></li>
443                                <li><a href="/tiktok-downloader"><i class="ph-duotone ph-download-simple text-pink-500"></i> TikTok Video Downloader</a></li>
444                            </ul>
445                        </div>
446
447                        <!-- SUBTITLES DROPDOWN -->
448                        <div class="dropdown dropdown-hover">
449                            <label tabindex="0" class="btn btn-ghost btn-sm gap-1 px-3">
450                                <i class="ph-duotone ph-subtitles text-violet-600"></i>
451                                Subtitles
452                                <i class="ph-duotone ph-caret-down text-xs"></i>
453                            </label>
454                            <ul tabindex="0"
455                                class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-72">
456                                <li><a href="/subtitles-and-captions" class="font-bold"><i class="ph-duotone ph-closed-captioning text-violet-600"></i> All Subtitle &amp; Caption Tools</a></li>
457                                <div class="divider my-0"></div>
458                                <li><a href="/subtitle-downloader"><i class="ph-duotone ph-download-simple text-violet-500"></i> Download YouTube Subtitles</a></li>
459                                <li><a href="/generate-subtitles"><i class="ph-duotone ph-magic-wand text-indigo-500"></i> Generate Subtitles</a></li>
460                                <li><a href="/video-to-srt"><i class="ph-duotone ph-file-video text-blue-500"></i> Video to SRT</a></li>
461                                <li><a href="/audio-to-srt"><i class="ph-duotone ph-file-audio text-purple-500"></i> Audio to SRT</a></li>
462                                <li><a href="/video-to-vtt"><i class="ph-duotone ph-file-code text-sky-500"></i> Video to VTT</a></li>
463                                <li><a href="/add-captions-to-video"><i class="ph-duotone ph-closed-captioning text-emerald-500"></i> Add Captions to Video</a></li>
464                                <div class="divider my-0"></div>
465                                <li><a href="/blog/how-to-download-youtube-subtitles"><i class="ph-duotone ph-book-open text-slate-500"></i> Guide: Download YouTube Subtitles</a></li>
466                            </ul>
467                        </div>
468
469                        <li><a href="/files" class="px-3">Files <span
470                                    class="text-xs font-bold text-blue-600 align-top">(0)</span></a></li>
471                        <li><a href="/refer" class="px-3 text-indigo-600 font-semibold shrink-0">
472                                <i class="ph-duotone ph-gift text-base align-text-bottom mr-1"></i>
473                                Refer</a></li>
474                        <li class="hidden xl:flex"><a href="/affiliate" class="px-3 text-emerald-600 font-semibold shrink-0">
475                                <i class="ph-duotone ph-handshake text-base align-text-bottom mr-1"></i>
476                                Affiliate</a></li>
477                    </ul>
478                </div>
479                <!-- START: MODIFIED SECTION -->
480                <!-- END: MODIFIED SECTION -->
481                <div class="navbar-end gap-2 w-auto flex-none">
482                    <!-- DOWNLOADERS DROPDOWN (BEFORE PRICING) -->
483                    <div class="dropdown dropdown-hover hidden lg:block self-center mr-2">
484                        <label tabindex="0" class="btn btn-ghost btn-sm gap-1 px-3">
485                            <i class="ph-duotone ph-caret-down text-xs hidden xl:inline"></i>
486                            <span class="hidden xl:inline">Downloaders</span>
487                            <span class="inline xl:hidden text-2xl mb-1 leading-none">...</span>
488                        </label>
489                        <ul tabindex="0"
490                            class="dropdown-content right-0 z-[2] menu p-2 shadow-2xl bg-white rounded-2xl w-56 border border-slate-100">
491                            <li class="menu-title px-4 py-2 text-xs opacity-50 uppercase tracking-widest font-bold">
492                                Media Downloaders</li>
493                            <li><a href="/youtube-downloader" class="flex items-center gap-3 py-3"><i
494                                        class="ph-duotone ph-youtube-logo text-xl text-red-600"></i> YouTube
495                                    Downloader</a></li>
496                            <li><a href="/facebook-downloader" class="flex items-center gap-3 py-3"><i
497                                        class="ph-duotone ph-facebook-logo text-xl text-blue-600"></i> Facebook
498                                    Downloader</a></li>
499                            <li><a href="/tiktok-downloader" class="flex items-center gap-3 py-3"><i
500                                        class="ph-duotone ph-tiktok-logo text-xl text-slate-800"></i> TikTok
501                                    Downloader</a></li>
502                            <li><a href="/instagram-downloader" class="flex items-center gap-3 py-3"><i
503                                        class="ph-duotone ph-instagram-logo text-xl text-pink-600"></i> Instagram
504                                    Downloader</a></li>
505                            <li><a href="/x-downloader" class="flex items-center gap-3 py-3"><i
506                                        class="ph-duotone ph-x-logo text-xl text-slate-900"></i> Twitter (X)
507                                    Downloader</a></li>
508                            <li><a href="/subtitle-downloader" class="flex items-center gap-3 py-3"><i
509                                        class="ph-duotone ph-subtitles text-xl text-violet-600"></i> Subtitle
510                                    Downloader</a></li>
511                        </ul>
512                    </div>
513
514                    
515                    <!-- GUEST USER -->
516                    <a href="/premium"
517                        class="btn btn-ghost btn-sm hidden sm:inline-flex">Pricing</a>
518
519                    <div class="tooltip tooltip-bottom" data-tip="Register">
520                        <a href="/register"
521                            class="btn btn-ghost btn-sm sm:btn-warning sm:text-black sm:hover:bg-yellow-300 sm:hover:border-yellow-400">
522                            <i class="ph-duotone ph-user-plus text-2xl"></i>
523                            <span class="hidden sm:inline sm:ml-1">Register</span>
524                        </a>
525                    </div>
526                    <div class="tooltip tooltip-bottom" data-tip="Login">
527                        <a href="/login"
528                            class="btn btn-ghost btn-sm sm:btn-primary sm:hover:bg-blue-500 sm:hover:border-blue-600">
529                            <i class="ph-duotone ph-sign-in text-2xl"></i>
530                            <span class="hidden sm:inline sm:ml-1">Login</span>
531                        </a>
532                    </div>
533                    
534                </div>
535            </nav>
536
537            <!-- PROMOTION BAR (Configurable via app.py) -->
538            
539
540            <main class="flex-grow w-full">
541                
542
543                
544
545
546                
547
548
549
550
551
552<div class="bg-blue-50 text-blue-900 py-2.5 px-4 text-center text-sm border-b border-blue-100">
553    <div class="max-w-4xl mx-auto flex items-center justify-center gap-2">
554        <i class="ph-fill ph-user-circle-plus text-lg"></i>
555        <p>
556            <strong>Don't lose your work!</strong>
557            <a href="/register" class="underline font-bold hover:text-blue-700">Sign Up / Login</a>
558            to permanently store and organize your transcriptions in your File Manager.
559        </p>
560    </div>
561</div>
562
563<section class="hero bg-blue-600 text-white" id="hero-form">
564    <div class="hero-content text-center w-full py-10">
565        <div class="w-full max-w-xl">
566            
567            
568            <h1 class="text-4xl font-bold">
569                Get a Youtube Transcript
570            </h1>
571            <p class="py-4">
572                Enter YouTube Video ID or URL
573                below.
574            </p>
575            
576
577            
578            <form id="transcript-form" class="flex flex-col mx-auto sm:flex-row sm:justify-center"
579                action="/tube"
580                method="get">
581                <input type="text" name="v"
582                    placeholder="Paste YouTube URL or Video ID here..."
583                    value=""
584                    class="w-full mb-2 truncate input input-bordered input-rounded sm:w-sm sm:mb-0 sm:mr-2 text-black"
585                    autocomplete="on" required />
586                <button type="submit"
587                    class="w-full btn btn-warning text-black btn-rounded sm:w-auto hover:bg-yellow-300 hover:border-yellow-400">
588                    <i class="ph-duotone ph-magnifying-glass text-lg mr-1"></i>
589                    Get Transcript
590                </button>
591            </form>
592        </div>
593    </div>
594</section>
595
596<div class="w-full max-w-7xl mx-auto px-4 mt-6">
597    <!-- USAGE BOX / GUEST & FREE USER PROMPT -->
598    
599    <div class="w-full flex flex-col md:flex-row justify-between items-center gap-4 mb-6 px-2 py-2">
600
601        <!-- LEFT: Usage Stats + Upgrade -->
602        
603
604        <!-- RIGHT: Prompts / Actions -->
605        <div
606            class="flex flex-col md:flex-row items-center justify-center md:justify-end gap-3 w-full md:w-auto text-center md:text-right">
607
608            <!-- GUEST VIEW -->
609            
610            <div class="hidden md:block">
611                <p class="text-sm text-base-content/80 font-medium">Don't lose your work!</p>
612                <p class="text-xs text-base-content/60">Permanently store and organize your transcriptions in your File
613                    Manager.</p>
614            </div>
615            <!-- Mobile Only Text (Simplified) -->
616            <div class="block md:hidden mb-2">
617                <p class="text-sm text-base-content/80 font-medium">Don't lose your work! Login to save.</p>
618            </div>
619
620            <a href="/register" class="btn btn-primary btn-sm text-white">
621                Sign Up / Login
622            </a>
623
624            <!-- REGISTERED (FREE) USER VIEW -->
625            
626        </div>
627    </div>
628    
629</div>
630
631<article class="w-full py-8 bg-base-100">
632    <div class="w-full max-w-7xl mx-auto px-4">
633
634        
635
636        <!-- START: NEW CONTENT FOR EMPTY PAGE (SPLIT BY TYPE) -->
637        
638        <div class="w-full max-w-2xl mx-auto px-4 py-8">
639
640            
641            
642            <div class="flex flex-col justify-center text-base-content">
643                <h2
644                    class="text-transform-none -rotate-1 text-2xl font-bold mb-4 inline-flex py-[0.9rem] px-4 justify-start w-fit rounded-box bg-blue-600 text-blue-50">
645                    How to Generate a YouTube Transcript 🎥
646                </h2>
647                <ul class="mb-8 list-decimal list-inside space-y-2">
648                    <li>Paste the YouTube video link into the box above and click "Get Transcript."</li>
649                    <li>
650                        View the full transcript with clickable timestamps that sync with the video player.
651                    </li>
652                    <li>
653                        Use the controls to switch languages or copy the text for your notes!
654                    </li>
655                </ul>
656
657                <h2
658                    class="text-transform-none -rotate-1 text-2xl font-bold mb-4 inline-flex py-[0.9rem] px-4 justify-start w-fit rounded-box bg-green-600 text-green-50">
659                    Why you need a YouTube Transcript? 🤔
660                </h2>
661                <p class="mb-8">
662                    YouTube videos are invaluable for learning and entertainment.
663                    However, sometimes you need to quickly find specific information,
664                    <strong class="">take notes</strong>, or share the content with
665                    someone who <strong class="">prefers reading</strong>. This tool
666                    makes it a breeze to get the full text from any YouTube video.
667                </p>
668
669                <h3
670                    class="text-transform-none -rotate-1 text-xl font-semibold mb-4 inline-flex py-[0.5rem] px-4 justify-start w-fit roun
670ded-box bg-yellow-500 text-black">
671                    For Note Takers & Students 📝
672                </h3>
673                <p class="mb-8">
674                    It can be challenging to follow along with a video while taking
675                    notes. With the transcript, you can easily
676                    <strong class="">copy and paste</strong> the text into your notes. Use our AI-powered premium
677                    features to instantly summarize the content or analyze its sentiment, saving you even more time.
678                </p>
679
680                <h3
681                    class="text-transform-none -rotate-1 text-xl font-semibold mb-4 inline-flex py-[0.5rem] px-4 justify-start w-fit rounded-box bg-purple-600 text-purple-50">
682                    For Content Creators 🎬
683                </h3>
684                <p class="mb-2">
685                    Content creators can use this tool to research and repurpose content effortlessly.
686                </p>
687                <ul class="mb-8 list-disc list-inside space-y-2">
688                    <li class="">
689                        <strong>Summarize the transcript</strong> and add it to the video description to <strong
690                            class="">rank higher in search engines.</strong>
691                    </li>
692                    <li class="">
693                        Extract key points from the transcript to <strong class="">create engaging social media
694                            posts</strong> that entice viewers to watch the full video.
695                    </li>
696                    <li class="">
697                        Use the transcript as a foundation for blog posts, articles, or detailed show notes related to
698                        your video content.
699                    </li>
700                </ul>
701            </div>
702            
703        </div>
704        
705        <!-- END: NEW CONTENT FOR EMPTY PAGE -->
706
707
708        
709    </div>
710
711    <!-- SCROLL TO AI BUTTON -->
712    <button id="scroll-to-ai-btn" class="hidden fixed bottom-5 right-5 btn btn-primary btn-circle z-50 shadow-lg">
713        <i class="ph-duotone ph-sparkle text-2xl"></i>
714    </button>
715
716
717    <!-- DOWNLOAD MODAL -->
718    <!-- DOWNLOAD MODAL -->
719    <dialog id="download_modal" class="modal">
720        <div class="modal-box w-11/12 max-w-3xl">
721            <div class="flex justify-between items-center mb-4">
722                <h3 class="font-bold text-lg">Download Media</h3>
723                <form method="dialog"><button class="btn btn-sm btn-circle btn-ghost">✕</button></form>
724            </div>
725
726            <div id="download_header_text">
727                <p class="py-2 text-sm text-gray-500">
728                    Extracting all available formats (Audio, Video, HD) costs <span class="font-bold text-primary">2
729                        Credits</span>.
730                </p>
731            </div>
732
733            <div id="download_loader" class="hidden flex flex-col items-center justify-center py-8">
734                <span class="loading loading-spinner loading-lg text-warning"></span>
735                <p class="text-sm mt-2 text-gray-400">Fetching all formats via secure connection...</p>
736            </div>
737
738            <div id="download_options" class="overflow-x-auto mt-4">
739                <!-- Table or Upsell will be injected here -->
740            </div>
741        </div>
742    </dialog>
743
744
745</article>
746
747<dialog id="auth_modal" class="modal">
748    <div class="modal-box">
749        <div class="text-center">
750            <svg xmlns="http://www.w3.org/2000/svg" class="w-16 h-16 mx-auto text-blue-500" viewBox="0 0 256 256">
751                <path
752                    d="M208,80H176V56a48,48,0,0,0-96,0V80H48A16,16,0,0,0,32,96V208a16,16,0,0,0,16,16H208a16,16,0,0,0,16-16V96A16,16,0,0,0,208,80Zm-80,84a12,12,0,1,1,12-12A12,12,0,0,1,128,164Zm32-84H96V56a32,32,0,0,1,64,0Z"
753                    opacity="0.2"></path>
754                <path
755                    d="M208,72H168V56a40,40,0,0,0-80,0V72H48A24,24,0,0,0,24,96V208a24,24,0,0,0,24,24H208a24,24,0,0,0,24-24V96A24,24,0,0,0,208,72Zm-80,96a16,16,0,1,1,16-16A16,16,0,0,1,128,168ZM152,72H104V56a24,24,0,0,1,48,0Z">
756                </path>
757            </svg>
758            <h3 id="auth-modal-title" class="mt-2 text-2xl font-bold">Unlock AI Features</h3>
759            <p id="auth-modal-text" class="py-4">To use our AI tools, please create a free account or log in.</p>
760        </div>
761
762        <div class="space-y-3 mt-4">
763            <a href="/login/google" data-auth-link class="btn btn-block">
764                <i class="fa-brands fa-google text-lg"></i>
765                Continue with Google
766            </a>
767            <a href="/login/microsoft" data-auth-link class="btn btn-block">
768                <i class="fa-brands fa-microsoft text-lg"></i>
769                Continue with Microsoft
770            </a>
771        </div>
772
773        <div class="divider text-sm text-gray-500 font-medium my-4">OR</div>
774
775        <div class="flex gap-4">
776            <a href="/login" data-auth-link class="btn flex-1">
777                <i class="ph-duotone ph-sign-in text-lg"></i>
778                Login
779            </a>
780            <a href="/register" data-auth-link
781                class="btn btn-warning text-black flex-1 hover:bg-yellow-300 hover:border-yellow-400">
782                <i class="ph-duotone ph-user-plus text-lg"></i>
783                Sign Up
784            </a>
785        </div>
786    </div>
787    <form method="dialog" class="modal-backdrop">
788        <button>close</button>
789    </form>
790</dialog>
791<script>
792    // openAuthModal({title, text, next}) — the auth modal with its own wording
793    // and a return address.
794    //
795    // Several pages open this modal directly for AI features, so it has to stay
796    // correct for them: whatever a caller changes is put back when the modal
797    // closes. `next` is appended to all four links (both OAuth providers, login,
798    // register) so the account is created and the visitor lands back where they
799    // were, instead of on /premium with the transcript they came for gone. The
800    // server validates it with _safe_next, so a hostile value is simply dropped.
801    (function () {
802        const modal = document.getElementById('auth_modal');
803        if (!modal) return;
804        const title = document.getElementById('auth-modal-title');
805        const text = document.getElementById('auth-modal-text');
806        const links = Array.prototype.slice.call(modal.querySelectorAll('[data-auth-link]'));
807        const defaults = {
808            title: title ? title.textContent : '',
809            text: text ? text.textContent : '',
810            hrefs: links.map(function (a) { return a.getAttribute('href'); })
811        };
812
813        function reset() {
814            if (title) title.textContent = defaults.title;
815            if (text) text.textContent = defaults.text;
816            links.forEach(function (a, i) { a.setAttribute('href', defaults.hrefs[i]); });
817        }
818        modal.addEventListener('close', reset);
819
820        window.openAuthModal = function (opts) {
821            opts = opts || {};
822            reset();
823            if (opts.title && title) title.textContent = opts.title;
824            if (opts.text && text) text.textContent = opts.text;
825            if (opts.next) {
826                links.forEach(function (a, i) {
827                    const base = defaults.hrefs[i];
828                    a.setAttribute('href', base + (base.indexOf('?') === -1 ? '?' : '&') +
829                                   'next=' + encodeURIComponent(opts.next));
830                });
831            }
832            try { modal.showModal(); } catch (_) { modal.setAttribute('open', ''); }
833        };
834    })();
835</script>
835
836
837<dialog id="confirm_email_modal" class="modal">
838    <div class="modal-box">
839        <div class="text-center">
840            <i class="ph-duotone ph-envelope-simple text-5xl text-blue-500 mx-auto"></i>
841            <h3 class="mt-2 text-2xl font-bold">Please Confirm Your Email</h3>
842            <p class="py-2 text-sm">To use AI features on the free plan, you need to confirm your email address. A
843                confirmation link was sent to <strong>your
844                    email</strong>.</p>
845            <p class="py-2 text-sm">Alternatively, <strong>upgrade to any premium plan</strong> to unlock all features
846                immediately, without email confirmation.</p>
847
848            <div id="resend-message" class="text-green-600 text-sm mt-2 font-semibold min-h-[1.25rem]"></div>
849            <button id="resend-confirm-btn" class="btn btn-link p-0 h-auto min-h-0 text-sm">
850                <span class="btn-text">Resend confirmation email</span>
851                <span class="btn-loader hidden loading loading-spinner loading-xs ml-2"></span>
852            </button>
853        </div>
854        <div class="modal-action mt-4 grid grid-cols-2 gap-4">
855            <form method="dialog" class="contents">
856                <button class="btn w-full">Maybe Later</button>
857            </form>
858            <button id="upgrade-from-confirm-btn"
859                class="btn btn-warning text-black w-full flex-nowrap items-center justify-center">
860                <i class="ph-duotone ph-crown"></i>
861                <span class="ml-2">Upgrade Now</span>
862            </button>
863        </div>
864    </div>
865    <form method="dialog" class="modal-backdrop">
866        <button>close</button>
867    </form>
868</dialog>
869
870<dialog id="premium_required_modal" class="modal">
871    <div class="modal-box">
872        <div class="text-center">
873            <i class="ph-duotone ph-rocket-launch text-5xl text-blue-500 mx-auto"></i>
874            <h3 class="mt-2 text-2xl font-bold">Upgrade to Unlock This Feature</h3>
875            <p class="py-2 text-sm">The <strong id="premium-feature-name"></strong> feature is available on our premium
876                plans. Upgrade now to get instant access to all AI tools.</p>
877        </div>
878        <div class="modal-action mt-4 grid grid-cols-2 gap-4">
879            <form method="dialog" class="contents">
880                <button class="btn w-full">Maybe Later</button>
881            </form>
882            <button id="upgrade-from-premium-modal-btn"
883                class="btn btn-warning text-black w-full flex-nowrap items-center justify-center">
884                <i class="ph-duotone ph-crown"></i>
885                <span class="ml-2">Upgrade Now</span>
886            </button>
887        </div>
888    </div>
889    <form method="dialog" class="modal-backdrop">
890        <button>close</button>
891    </form>
892</dialog>
893
894<dialog id="feedback_modal" class="modal">
895    <div class="modal-box relative">
896        <form method="dialog">
897            <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
898        </form>
899        <h3 class="font-bold text-lg">Submit Feedback</h3>
900        <form action="/submit-feedback" method="post" class="py-4">
901            <div class="form-control">
902                <label class="label">
903                    <span class="label-text">From</span>
904                </label>
905                <input type="text" name="from" placeholder="Your Name" class="input input-bordered" required>
906            </div>
907            <div class="form-control">
908                <label class="label">
909                    <span class="label-text">Email</span>
910                </label>
911                <input type="email" name="email" placeholder="[email protected]" class="input input-bordered" required>
912            </div>
913            <div class="form-control">
914                <label class="label">
915                    <span class="label-text">Feedback</span>
916                </label>
917                <textarea name="feedback" class="textarea textarea-bordered h-24" placeholder="Your feedback..."
918                    required></textarea>
919            </div>
920            <div class="modal-action">
921                <button type="submit" class="btn btn-primary">Submit</button>
922                <button type="button" class="btn"
923                    onclick="document.getElementById('feedback_modal').close()">Close</button>
924            </div>
925        </form>
926    </div>
927    <form method="dialog" class="modal-backdrop">
928        <button>close</button>
929    </form>
930</dialog>
931
932<!-- Refer & Earn Modal -->
933<dialog id="referral_modal" class="modal">
934    <div class="modal-box max-w-md relative">
935        <form method="dialog">
936            <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
937        </form>
938        <h3 class="font-bold text-2xl mb-2 flex items-center gap-2">
939            <i class="ph-duotone ph-gift text-blue-600"></i>
940            Refer & Earn Pro
941        </h3>
942        <p class="py-4 text-gray-600">
942 Get 6 friends to sign up and get 1 month of <span
943                class="font-bold text-blue-600">Pro Plan</span> for free!</p>
944
945        <div class="bg-gray-50 p-4 rounded-xl border border-gray-200 mb-6">
946            <label class="text-xs font-bold text-gray-500 uppercase mb-2 block">Your Unique Referral Link</label>
947            <div class="flex gap-2">
948                <input type="text" readonly id="referral-link-input"
949                    value="Login to get your link"
950                    class="input input-bordered w-full text-sm font-mono bg-white">
951                <button class="btn btn-primary" onclick="copyReferralLink()">
952                    <i class="ph-duotone ph-copy"></i>
953                </button>
954            </div>
955            <p id="copy-status" class="text-xs text-green-600 mt-2 hidden">Copied to clipboard!</p>
956        </div>
957
958        <div class="flex flex-col gap-3">
959            <div class="flex items-center justify-between text-sm">
960                <span class="text-gray-500">Your Referrals:</span>
961                <span class="font-bold text-blue-600">0/6</span>
962            </div>
963            <progress class="progress progress-primary w-full"
964                value="0" max="6"></progress>
965        </div>
966
967        <div class="modal-action">
968            <form method="dialog">
969                <button class="btn">Close</button>
970            </form>
971        </div>
972    </div>
973    
973<script>
974        function copyReferralLink() {
975            const input = document.getElementById('referral-link-input');
976            input.select();
977            input.setSelectionRange(0, 99999);
978            navigator.clipboard.writeText(input.value);
979
980            const status = document.getElementById('copy-status');
981            status.classList.remove('hidden');
982            setTimeout(() => status.classList.add('hidden'), 2000);
983        }
984    </script>
984
985    <form method="dialog" class="modal-backdrop">
986        <button>close</button>
987    </form>
988</dialog>
989
990
991<style>
992    /* Match premium.html's scroll-lock pattern: modal-BOX scrolls,
993       overscroll-behavior:contain stops scroll chaining to the page,
994       body locked via :has(). */
995    dialog#transcript_checkout_modal[open] {
996        position: fixed;
997        top: 0; right: 0; bottom: 0; left: 0;
998        width: 100%;
999        height: 100dvh;
1000        max-width: 100%;
1001        max-height: 100dvh;
1002        margin: 0;
1003        padding: 0;
1004        border: 0;
1005        background: rgba(0, 0, 0, 0.5);
1006        display: block;
1007        overflow: hidden;
1008        box-sizing: border-box;
1009    }
1010    dialog#transcript_checkout_modal[open] .modal-box {
1011        position: absolute !important;
1012        top: 0 !important;
1013        right: 0 !important;
1014        bottom: 0 !important;
1015        left: 0 !important;
1016        width: auto !important;
1017        max-width: none !important;
1018        height: auto !important;
1019        max-height: none !important;
1020        margin: 0 !important;
1021        border-radius: 0 !important;
1022        overflow-y: auto;
1023        overscroll-behavior: contain;
1024        -webkit-overflow-scrolling: touch;
1025    }
1026    @media (min-width: 768px) {
1027        dialog#transcript_checkout_modal[open] .modal-box {
1028            position: relative !important;
1029            top: auto !important;
1030            right: auto !important;
1031            bottom: auto !important;
1032            left: auto !important;
1033            width: 100% !important;
1034            max-width: 32rem !important;
1035            height: auto !important;
1036            max-height: 90dvh !important;
1037            margin: 2rem auto !important;
1038            border-radius: 1rem !important;
1039        }
1040        dialog#transcript_checkout_modal[open] {
1041            display: flex;
1042            align-items: center;
1043            justify-content: center;
1044        }
1045    }
1046    body:has(dialog#transcript_checkout_modal[open]) {
1047        overflow: hidden !important;
1048        touch-action: none;
1049    }
1050</style>
1051<dialog id="transcript_checkout_modal" class="modal">
1052    <div class="modal-box relative p-4 md:p-6">
1053        <form method="dialog">
1054            <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2 z-10">✕</button>
1055        </form>
1056        <div class="text-center mb-2 md:mb-4">
1057            <i class="ph-duotone ph-rocket-launch text-3xl md:text-5xl text-blue-500 mx-auto"></i>
1058            <h3 class="mt-1 md:mt-2 text-lg md:text-2xl font-bold">Upgrade to Continue</h3>
1059
1060
1061            <div class="py-2 text-sm hidden md:block text-left">
1062                <p class="mb-2">
1063                    You've used all your credits. Complete your purchase for the
1064                    <strong>Creator Plan</strong> to:
1065                </p>
1066
1067                <ul class="space-y-1">
1068                    <li>✅ Unlock more AI features!</li>
1069                    <li>✅ No ads!</li>
1070                    <li>✅ Prioritized conversion</li>
1071                    <li>✅ Download YouTube Videos</li>
1072                </ul>
1073            </div>
1074
1075
1076        </div>
1077        <div id="transcript-checkout">
1078            <div class="flex justify-center items-center p-8">
1079                <span class="loading loading-dots loading-lg"></span>
1080            </div>
1081        </div>
1082    </div>
1083    <form method="dialog" class="modal-backdrop">
1084        <button>close</button>
1085    </form>
1086</dialog>
1087
1088
1089
1090<!-- NEW: Free Trial Modal -->
1091<dialog id="free_trial_modal" class="modal">
1092    <div class="modal-box relative bg-white border-t-8 border-blue-600">
1093        <form method="dialog">
1094            <button class="btn btn-sm btn-circle btn-ghost absolute right-4 top-4">✕</button>
1095        </form>
1096        <h3 class="font-bold text-2xl text-center mb-2">You've hit your daily limit</h3>
1097        <p class="py-2 text-center text-gray-600">
1098            Resets in <span class="font-bold text-red-600">24 Hours</span>. Try
1099            our Pro plan for FREE to continue.
1100        </p>
1101
1102        <div class="py-4">
1103            <ul class="space-y-2 mb-6">
1104                <li class="flex items-center"><i class="ph-duotone ph-check-circle text-green-500 mr-2"></i> Unlimited
1105                    Transcripts</li>
1106                <li class="flex items-center"><i class="ph-duotone ph-check-circle text-green-500 mr-2"></i> Advanced AI
1107                    Summaries</li>
1108                <li class="flex items-center"><i class="ph-duotone ph-check-circle text-green-500 mr-2"></i> 7 Days Free
1109                    ($0.00 today)</li>
1110            </ul>
1111
1112            
1113            <button type="button"
1114                class="btn btn-primary w-full text-lg shadow-lg hover:scale-105 transition-transform choose-plan-btn"
1115                data-plan-name="premium_yearly" data-trial="true">
1116                Start My 7-Day Free Trial
1117            </button>
1118            
1119            <div class="text-center mt-3">
1120                <a href="/" class="link link-hover text-sm text-gray-500">No thanks, take me
1121                    home</a>
1122            </div>
1123        </div>
1124    </div>
1125    <form method="dialog" class="modal-backdrop">
1126        <button>close</button>
1127    </form>
1128</dialog>
1129
1130
1131
1132<!-- NEW: Pay Prompt Modal (Exceeding Limit) -->
1133<style>
1134    /* Always-visible scrollbar for the paywall form.
1135       Chrome/Safari hide overlay scrollbars until a scroll is already in
1136       progress, so nothing indicates the form continues below the fold. Forcing
1137       the track to render makes the remaining length obvious at a glance. */
1138    #paywall-scroll {
1139        scrollbar-width: thin;                       /* Firefox */
1140        scrollbar-color: #cbd5e1 #f1f5f9;
1141        scrollbar-gutter: stable;                    /* no reflow when it appears */
1142    }
1143    #paywall-scroll::-webkit-scrollbar {
1144        width: 10px;
1145        -webkit-appearance: none;                    /* opt out of overlay style */
1146    }
1147    #paywall-scroll::-webkit-scrollbar-track {
1148        background: #f1f5f9;
1149        border-radius: 8px;
1150    }
1151    #paywall-scroll::-webkit-scrollbar-thumb {
1152        background: #cbd5e1;
1153        border-radius: 8px;
1154        border: 2px solid #f1f5f9;
1155    }
1156    #paywall-scroll::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
1157</style>
1158
1159<dialog id="pay_prompt_modal" class="modal">
1160    
1161    <div class="modal-box relative bg-white border-t-8 border-yellow-400 p-0 overflow-hidden
1162                w-screen max-w-none h-[100dvh] max-h-[100dvh] rounded-none
1163                md:w-[92vw] md:max-w-[960px] md:h-[88vh] md:max-h-[880px] md:rounded-2xl">
1164        <div class="flex flex-col md:flex-row h-full">
1165            <!-- Left Side: Visuals & Benefits -->
1166            <div
1167                class="hidden md:flex md:w-2/5 bg-gradient-to-br from-blue-600 to-indigo-700 text-white p-8 flex-col justify-between">
1168                <div>
1169                    <h3 class="text-3xl font-bold mb-6">Unlimited Access</h3>
1170                    <ul class="space-y-4">
1171                        <li class="flex items-start gap-3">
1172                            <i class="ph-duotone ph-check-circle text-2xl text-yellow-300"></i>
1173                            <span class="text-lg">Unlimited Transcripts</span>
1174                        </li>
1175                        <li class="flex items-start gap-3">
1176                            <i class="ph-duotone ph-sparkle text-2xl text-yellow-300"></i>
1177                            <span class="text-lg">Full AI Power (Summaries, Notes, etc.)</span>
1178                        </li>
1179                        <li class="flex items-start gap-3">
1180                            <i class="ph-duotone ph-clock text-2xl text-yellow-300"></i>
1181                            <span class="text-lg">No Daily Wait Time</span>
1182                        </li>
1183                        <li class="flex items-start gap-3">
1184                            <i class="ph-duotone ph-file-arrow-down text-2xl text-yellow-300"></i>
1185                            <span class="text-lg">SRT & VTT Export Support</span>
1186                        </li>
1187                    </ul>
1188                </div>
1189                <div class="text-sm opacity-80">
1190                    Trusted by 50,000+ creators and students.
1191                </div>
1192            </div>
1193
1194            <!-- Right Side: compact fixed header, scrolling form, thin footer -->
1195            <div class="flex-1 flex flex-col min-h-0">
1196
1197                
1198                
1199                <div class="shrink-0 px-4 md:px-8 pt-3 md:pt-5 pb-3 border-b border-gray-100">
1200                    <div class="flex items-center justify-between gap-2">
1201                        
1202                        <h2 id="paywall-headline"
1203                            class="text-lg md:text-2xl font-extrabold text-gray-900 whitespace-nowrap">
1204                            You've hit your daily limit!
1205                        </h2>
1206                        
1207                        <form method="dialog" class="shrink-0">
1208                            <button aria-label="Close"
1209                                class="btn btn-sm md:btn-md btn-circle bg-gray-100 hover:bg-gray-200
1210                                       border-0 text-gray-600 text-base md:text-lg shadow-sm">✕</button>
1211                        </form>
1212                    </div>
1213                </div>
1214
1215                
1216                <div id="paywall-scroll" class="relative flex-1 overflow-y-auto px-4 md:px-8 py-3"
1217                     style="-webkit-overflow-scrolling: touch;">
1218                    
1219                    <div id="paywall-sb" class="pointer-events-none absolute top-2 right-1 bottom-2 w-1.5
1220                                                rounded-full bg-gray-200/70 opacity-0 transition-opacity">
1221                        <div id="paywall-sb-thumb"
1222                             class="w-full rounded-full bg-gray-400/80"></div>
1223                    </div>
1224                    
1225                    <div id="paywall-cta" class="py-6">
1226                        
1227                        
1228                        <a id="pay-prompt-signup" href="/register"
1229                            class="btn btn-warning btn-lg btn-block text-black shadow-xl
1230                                   hover:scale-105 transition-all duration-200 py-4 h-auto text-xl">
1231                            <i class="ph-duotone ph-user-plus text-2xl mr-2"></i>
1232                            Create Free Account
1233                        </a>
1234                        <p class="mt-3 text-sm text-gray-400">
1235                            Sign up in seconds — then unlock a 7-day free trial
1236                        </p>
1237                        
1238                        <a href="/premium"
1239                           class="link link-hover text-gray-400 text-xs mt-2 inline-block">Compare all plans</a>
1240                    </div>
1241
1242                    <div id="paywall-checkout" class="w-full min-h-[300px] hidden"
1243                         data-plan-name="premium_yearly" data-trial="true"
1244                         data-upgrade-cta="paywall_guest">
1245                        <div class="flex justify-center items-center p-8">
1246                            <span class="loading loading-dots loading-lg"></span>
1247                        </div>
1248                    </div>
1249                </div>
1250
1251                
1252            </div>
1253        </div>
1254    </div>
1255    <form method="dialog" class="modal-backdrop">
1256        <button>close</button>
1257    </form>
1258</dialog>
1259
1260<!-- CHECKOUT MODAL (Global) -->
1261
1262<style>
1263    dialog#checkout_modal[open] {
1264        position: fixed;
1265        top: 0; right: 0; bottom: 0; left: 0;
1266        width: 100%;
1267        height: 100dvh;
1268        max-width: 100%;
1269        max-height: 100dvh;
1270        margin: 0;
1271        padding: 0;
1272        border: 0;
1273        background: rgba(0, 0, 0, 0.5);
1274        display: block;
1275        overflow: hidden;
1276        box-sizing: border-box;
1277    }
1278    /* Absolute-positioned modal-box covers the full dialog edge-to-edge
1279       on mobile, side-stepping flex sizing where DaisyUI's .modal-box
1280       won the width race even with !important. */
1281    dialog#checkout_modal[open] .modal-box {
1282        position: absolute !important;
1283        top: 0 !important;
1284        right: 0 !important;
1285        bottom: 0 !important;
1286        left: 0 !important;
1287        width: auto !important;
1288        max-width: none !important;
1289        height: auto !important;
1290        max-height: none !important;
1291        margin: 0 !important;
1292        border-radius: 0 !important;
1293        overflow-y: auto;
1294        overscroll-behavior: contain;
1295        -webkit-overflow-scrolling: touch;
1296    }
1297    @media (min-width: 768px) {
1298        dialog#checkout_modal[open] .modal-box {
1299            position: relative !important;
1300            top: auto !important;
1301            right: auto !important;
1302            bottom: auto !important;
1303            left: auto !important;
1304            width: 100% !important;
1305            max-width: 32rem !important;
1306            height: auto !important;
1307            max-height: 90dvh !important;
1308            margin: 2rem auto !important;
1309            border-radius: 1rem !important;
1310        }
1311        dialog#checkout_modal[open] {
1312            display: flex;
1313            align-items: center;
1314            justify-content: center;
1315        }
1316    }
1317</style>
1318<dialog id="checkout_modal" class="modal">
1319    <div class="modal-box p-0 relative">
1320        <form method="dialog">
1321            <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2 z-50">✕</button>
1322        </form>
1323        <div id="checkout" style="min-height: 50dvh;">
1324            <div class="flex justify-center items-center p-8">
1325                <span class="loading loading-dots loading-lg"></span>
1326            </div>
1327        </div>
1328    </div>
1329    <form method="dialog" class="modal-backdrop">
1330        <button>close</button>
1331    </form>
1332</dialog>
1333
1334<script>
1335    // Defined here, at the end of the partial, because it needs #pay_prompt_modal
1336    // to exist: the auth-modal script above runs while the page is still parsing
1337    // and that dialog is declared several hundred lines below it, so a copy of
1338    // this living up there found null, returned early, and left window.openPaywall
1339    // undefined — every caller silently fell back to the daily-limit wording.
1340        // openPaywall({title, cta}) — the same paywall, worded for the thing the
1341        // visitor just tried to do, and tagged so each trigger is measurable
1342        // separately. "You've hit your daily limit!" is wrong copy for someone
1343        // who clicked SRT or just finished a download. Everything is restored on
1344        // close, because the daily-limit path opens this same modal.
1345    (function () {
1346            const pw = document.getElementById('pay_prompt_modal');
1347            if (!pw) return;
1348            const head = document.getElementById('paywall-headline');
1349            const mount = document.getElementById('paywall-checkout');
1350            const defaults = {
1351                title: head ? head.textContent.trim() : '',
1352                cta: mount ? mount.getAttribute('data-upgrade-cta') : null
1353            };
1354            pw.addEventListener('close', function () {
1355                if (head) head.textContent = defaults.title;
1356                if (mount && defaults.cta) mount.setAttribute('data-upgrade-cta', defaults.cta);
1357            });
1358            window.openPaywall = function (opts) {
1359                opts = opts || {};
1360                if (opts.title && head) head.textContent = opts.title;
1361                if (opts.cta && mount) mount.setAttribute('data-upgrade-cta', opts.cta);
1362                try { pw.showModal(); } catch (_) { pw.setAttribute('open', ''); }
1363            };
1364        })();
1365
1366</script>
1366
1367
1368<!-- templates/partials/_stripe_logic.html -->
1369<!-- Stripe.js v3 -->
1370<script src="https://js.stripe.com/v3/"></script>
1370
1371
1372<script>
1373    (function () {
1374        const stripe = Stripe("pk_live_51GYV9XIHI17dZ9P7DQ88NMUhg7MBvq3VIhBNOxMw1v4a3HOPk0N92IdHXeHvsnQO48BAdyFA2fTtJewjf5pqyabu0071HXg26E");
1375        const checkoutModal = document.getElementById('checkout_modal');
1376        let checkout = null; // To hold the Stripe Checkout instance
1377
1378        // ------------------------------------------------------------------
1379        // Session warming.
1380        //
1381        // Measured breakdown of click -> pay-button-visible: the POST to
1382        // /create-checkout-session accounts for 1.4-1.8s of it, because that
1383        // endpoint makes two sequential Stripe API calls (the embedded session
1384        // plus its hosted fallback). None of that work depends on the click —
1385        // only on which plan is being bought — so it can start on the first
1386        // real signal of intent and be finished, or nearly, by the time the
1387        // click lands.
1388        //
1389        // Intent is hover-with-dwell on desktop (a bare mouseenter fires on
1390        // every button the pointer crosses while scrolling), touchstart on
1391        // mobile, and focus for keyboard users.
1392        //
1393        // The cost is the same trade the paywall below already makes: sessions
1394        // are created for people who hover and never buy. Bounded by caching
1395        // one in-flight promise per plan per pageview, so a user who hovers the
1396        // same plan ten times still creates exactly one.
1397        // ------------------------------------------------------------------
1398        // Fire-and-forget beacon so we learn how often embedded checkout is
1399        // actually blocked. sendBeacon specifically, not fetch: the blocked path
1400        // navigates to the hosted URL on the very next line, and a normal fetch
1401        // would be cancelled by that navigation before it left the browser.
1402        function checkoutBeacon(event, surface, reason) {
1403            try {
1404                const body = JSON.stringify({
1405                    event: event, surface: surface, reason: String(reason || '').slice(0, 120)
1406                });
1407                if (navigator.sendBeacon) {
1408                    navigator.sendBeacon('/checkout-telemetry',
1409                        new Blob([body], { type: 'application/json' }));
1410                } else {
1411                    fetch('/checkout-telemetry', {
1412                        method: 'POST', headers: { 'Content-Type': 'application/json' },
1413                        body: body, keepalive: true
1414                    }).catch(function () {});
1415                }
1416            } catch (_) {}
1417        }
1418
1419        const HOVER_DWELL_MS = 80;
1420        const WARM_TTL_MS = 10 * 60 * 1000;   // don't reuse a session forever
1421        const warmCache = new Map();          // key -> {promise, at}
1422        const hoverTimers = new WeakMap();
1423
1424        function warmKey(planName, isTrial) { return planName + '|' + (isTrial ? 't' : 'f'); }
1425
1426        // `warm` marks a session created speculatively on hover rather than by
1427        // a click, so the server can count how many of these are never used.
1428        function requestSession(planName, isTrial, warm) {
1429            return fetch('/create-checkout-session', {
1430                method: 'POST',
1431                headers: { 'Content-Type': 'application/json' },
1432                body: JSON.stringify({
1433                    planName: planName,
1434                    returnUrl: window.location.href,
1435                    isTrial: isTrial,
1436                    warm: !!warm
1437                })
1438            }).then(r => r.json());
1439        }
1440
1441        function warmSession(planName, isTrial) {
1442            if (!planName) return null;
1443            const key = warmKey(planName, isTrial);
1444            const hit = warmCache.get(key);
1445            if (hit && (Date.now() - hit.at) < WARM_TTL_MS) return hit.promise;
1446
1447            // A rejected or error-carrying warm must not be cached as the
1448            // answer — drop it so the click retries cleanly rather than
1449            // inheriting a stale failure.
1450            const promise = requestSession(planName, isTrial, true)
1451                .then(payload => {
1452                    if (!payload || payload.error || !payload.clientSecret) warmCache.delete(key);
1453                    return payload;
1454                })
1455                .catch(e => { warmCache.delete(key); throw e; });
1456
1457            warmCache.set(key, { promise: promise, at: Date.now() });
1458            return promise;
1459        }
1460
1461        function warmFromButton(btn) {
1462            if (!btn || btn.classList.contains('cursor-default')) return;
1463            warmSession(btn.dataset.planName, btn.dataset.trial === 'true');
1464        }
1465
1466        function attachWarmHandlers(btn) {
1467            btn.addEventListener('mouseenter', () => {
1468                clearTimeout(hoverTimers.get(btn));
1469                hoverTimers.set(btn, setTimeout(() => warmFromButton(btn), HOVER_DWELL_MS));
1470            });
1471            btn.addEventListener('mouseleave', () => clearTimeout(hoverTimers.get(btn)));
1472            // Touch and keyboard are already deliberate; no dwell needed.
1473            btn.addEventListener('touchstart', () => warmFromButton(btn), { passive: true });
1474            btn.addEventListener('focus', () => warmFromButton(btn));
1475        }
1476
1477        // Helper function for checkout
1478        async function handleCheckout(btn) {
1479            if (btn.classList.contains('cursor-default')) return; // Current plan
1480
1481            const planName = btn.dataset.planName;
1482            const isTrial = btn.dataset.trial === 'true';
1483
1484            // Tracking — fire BEFORE we open the modal so we capture intent
1485            // even if the user bounces while Stripe is loading.
1486            try {
1487                if (window.__trackUpgradeOpen) {
1488                    window.__trackUpgradeOpen(
1489                        btn.dataset.upgradeCta || 'premium_choose_plan',
1490                        {plan_name: planName, is_trial: isTrial}
1491                    );
1492                }
1493            } catch (_) {}
1494
1495            if (checkoutModal) {
1496                checkoutModal.showModal();
1497            } else {
1498                console.error("Checkout modal not found.");
1499                return;
1500            }
1501
1502            let payload;
1503            try {
1504                // Started on hover/touch/focus, so this usually resolves
1505                // immediately. warmSession() returns the in-flight promise when
1506                // the request is still running, so a fast click waits for the
1507                // same request instead of firing a second one.
1508                payload = await (warmSession(planName, isTrial) || requestSession(planName, isTrial, false));
1509            } catch (e) {
1510                const checkoutDiv = document.getElementById('checkout');
1511                if (checkoutDiv) {
1512                    checkoutDiv.innerHTML = `<p class="text-red-500 p-8 text-center">Network error contacting our server.</p>`;
1513                }
1514                return;
1515            }
1516
1517            const { clientSecret, fallbackUrl, error } = payload;
1518
1519            if (error || !clientSecret) {
1520                if (fallbackUrl) { window.location.href = fallbackUrl; return; }
1521                const checkoutDiv = document.getElementById('checkout');
1522                if (checkoutDiv) {
1523                    checkoutDiv.innerHTML = `<p class="text-red-500 p-8 text-center">Could not initialize payment. ${error || ''}</p>`;
1524                }
1525                return;
1526            }
1527
1528            if (checkout) {
1529                checkout.destroy();
1530            }
1531
1532            try {
1533                checkout = await stripe.initEmbeddedCheckout({ clientSecret });
1534                const checkoutDiv = document.getElementById('checkout');
1535                if (checkoutDiv) {
1536                    checkoutDiv.innerHTML = '';
1537                    checkout.mount('#checkout');
1538                }
1539                checkoutBeacon('embedded_ok', 'modal');
1540            } catch (e) {
1541                checkoutBeacon('embedded_blocked', 'modal', e && e.message);
1542                // Embedded init failed (almost always tracking-protection
1543                // blocking m.stripe.network). Send the user to the hosted
1544                // checkout instead of leaving them with a broken modal.
1545                console.warn('Embedded init failed, falling back to hosted:', e);
1546                if (fallbackUrl) {
1547                    window.location.href = fallbackUrl;
1548                } else {
1549                    const checkoutDiv = document.getElementById('checkout');
1550                    if (checkoutDiv) {
1551                        checkoutDiv.innerHTML = `<p class="text-red-500 p-8 text-center">Could not load checkout. ${e.message || ''}</p>`;
1552                    }
1553                }
1554            }
1555        }
1556
1557        // Handle button clicks directly
1558        function attachCheckoutHandlers() {
1559            document.querySelectorAll('.choose-plan-btn').forEach(button => {
1560                // Remove old listeners if any (simple way: clone node)
1561                const newButton = button.cloneNode(true);
1562                button.parentNode.replaceChild(newButton, button);
1563
1564                newButton.addEventListener('click', (event) => {
1565                    event.preventDefault();
1566                    event.stopPropagation();
1567                    handleCheckout(event.currentTarget);
1568                });
1569                attachWarmHandlers(newButton);
1570            });
1571
1572            // Handle entire card clicks if present (on premium.html)
1573            document.querySelectorAll('.plan-card').forEach(card => {
1574                card.addEventListener('mouseenter', () => {
1575                    clearTimeout(hoverTimers.get(card));
1576                    hoverTimers.set(card, setTimeout(
1577                        () => warmFromButton(card.querySelector('.choose-plan-btn')), HOVER_DWELL_MS));
1578                });
1579                card.addEventListener('mouseleave', () => clearTimeout(hoverTimers.get(card)));
1580
1581                card.addEventListener('click', (event) => {
1582                    const button = card.querySelector('.choose-plan-btn');
1583                    const link = card.querySelector('a.btn');
1584
1585                    if (button) {
1586                        handleCheckout(button);
1587                    } else if (link) {
1588                        link.click();
1589                    }
1590                });
1591            });
1592        }
1593
1594        // Initial attachment
1595        if (document.readyState === "loading") {
1596            document.addEventListener("DOMContentLoaded", attachCheckoutHandlers);
1597        } else {
1598            attachCheckoutHandlers();
1599        }
1600
1601        // Also expose it to window for dynamic elements
1602        window.attachCheckoutHandlers = attachCheckoutHandlers;
1603
1604        // ------------------------------------------------------------------
1605        // Paywall: mount the embedded form directly, no button, no 2nd modal.
1606        //
1607        // The old flow was paywall modal -> click -> spinner -> a SECOND dialog
1608        // stacked on top. Roughly 30% of members dropped at that button
1609        // (2,309 paywall hits -> 1,628 clicks), and for guests it was a brand
1610        // new step they never used to face. Now the limit message and the card
1611        // form are one surface.
1612        //
1613        // Cost of doing it this way: a checkout session (plus its hosted
1614        // fallback) is created on every paywall view rather than per click, so
1615        // session volume tracks paywall hits. That is the deliberate trade.
1616        // ------------------------------------------------------------------
1617        const paywallModal = document.getElementById('pay_prompt_modal');
1618        const paywallMount = document.getElementById('paywall-checkout');
1619        let paywallLoaded = false;
1620
1621        async function loadPaywallCheckout() {
1622            if (paywallLoaded || !paywallMount) return;
1623            paywallLoaded = true;   // set first: an observer can fire twice
1624
1625            const planName = paywallMount.dataset.planName || 'premium_yearly';
1626            const isTrial = paywallMount.dataset.trial === 'true';
1627
1628            try {
1629                if (window.__trackUpgradeOpen) {
1630                    window.__trackUpgradeOpen(
1631                        paywallMount.dataset.upgradeCta || 'paywall_inline',
1632                        {plan_name: planName, is_trial: isTrial, inline: true}
1633                    );
1634                }
1635            } catch (_) {}
1636
1637            let payload;
1638            try {
1639                const response = await fetch('/create-checkout-session', {
1640                    method: 'POST',
1641                    headers: {'Content-Type': 'application/json'},
1642                    body: JSON.stringify({
1643                        planName: planName,
1644                        returnUrl: window.location.href,
1645                        isTrial: isTrial
1646                    })
1647                });
1648                payload = await response.json();
1649            } catch (e) {
1650                paywallMount.innerHTML =
1651                    '<p class="text-red-500 p-6 text-center">Network error — please retry.</p>';
1652                paywallLoaded = false;   // allow a retry on reopen
1653                return;
1654            }
1655
1656            const {clientSecret, fallbackUrl, error} = payload || {};
1657            if (error || !clientSecret) {
1658                // Already subscribed, invalid plan, or Stripe refused.
1659                paywallMount.innerHTML = fallbackUrl
1660                    ? '<a class="btn btn-warning btn-block text-black" href="' + fallbackUrl + '">Continue to checkout</a>'
1661                    : '<p class="text-red-500 p-6 text-center">' + (error || 'Could not start checkout.') + '</p>';
1662                return;
1663            }
1664
1665            try {
1666                if (checkout) { checkout.destroy(); checkout = null; }
1667                checkout = await stripe.initEmbeddedCheckout({clientSecret});
1668                paywallMount.innerHTML = '';
1669                checkout.mount('#paywall-checkout');
1670                checkoutBeacon('embedded_ok', 'paywall');
1671            } catch (e) {
1672                checkoutBeacon('embedded_blocked', 'paywall', e && e.message);
1673                // Embedded blocked (tracking protection / adblock). Offer the
1674                // hosted page rather than leaving an empty box.
1675                console.warn('Embedded init failed, offering hosted:', e);
1676                paywallMount.innerHTML = fallbackUrl
1677                    ? '<a class="btn btn-warning btn-block text-black" href="' + fallbackUrl + '">Continue to secure checkout</a>'
1678                    : '<p class="text-red-500 p-6 text-center">Could not load checkout.</p>';
1679            }
1680        }
1681
1682        // Trigger on the button, not on the modal opening. Auto-loading the
1683        // form for everyone who hit a limit removed the intent filter and
1684        // measurably cost conversions; the click is the signal.
1685        const paywallStartBtn = document.getElementById('paywall-start-btn');
1686        const paywallCta = document.getElementById('paywall-cta');
1687        if (paywallStartBtn && paywallMount) {
1688            paywallStartBtn.addEventListener('click', function () {
1689                // Swap the CTA for the form in place — one modal, no stacking.
1690                if (paywallCta) paywallCta.classList.add('hidden');
1691                paywallMount.classList.remove('hidden');
1692                loadPaywallCheckout();
1693            });
1694        }
1695        // Reset when the modal closes so reopening shows the CTA again rather
1696        // than a stale form bound to an expired session.
1697        if (paywallModal) {
1698            paywallModal.addEventListener('close', () => {
1699                if (paywallCta) paywallCta.classList.remove('hidden');
1700                if (paywallMount) paywallMount.classList.add('hidden');
1701                paywallLoaded = false;
1702                if (checkout) { try { checkout.destroy(); } catch (_) {} checkout = null; }
1703                paywallMount.innerHTML =
1704                    '<div class="flex justify-center items-center p-8">'
1705                    + '<span class="loading loading-dots loading-lg"></span></div>';
1706            });
1707        }
1708
1709        // ------------------------------------------------------------------
1710        // "Did they actually engage with the payment form?"
1711        //
1712        // Stripe's embed is a cross-origin iframe, so field-level events are
1713        // invisible to us — we cannot see which input was focused, or typed in.
1714        // What IS observable: when focus moves INTO that iframe the parent
1715        // window fires blur and document.activeElement becomes the iframe. That
1716        // is a reliable proxy for "clicked into the card form", which turns
1717        //     shown -> paid
1718        // into
1719        //     shown -> engaged -> paid
1720        // so an abandonment can be attributed to the offer or to the form.
1721        // Fires once per page view.
1722        // ------------------------------------------------------------------
1723        (function () {
1724            let sent = false;
1725            window.addEventListener('blur', function () {
1726                if (sent) return;
1727                const el = document.activeElement;
1728                if (!el || el.tagName !== 'IFRAME') return;
1729                if (!/stripe/i.test(el.name || '') && !/stripe/i.test(el.src || '')) return;
1730                const modal = document.getElementById('pay_prompt_modal');
1731                if (modal && !modal.hasAttribute('open')) return;
1732                sent = true;
1733                try {
1734                    fetch('/api/track', {
1735                        method: 'POST', credentials: 'same-origin',
1736                        headers: {'Content-Type': 'application/json'},
1737                        body: JSON.stringify({event: 'checkout_form_engaged', meta: {
1738                            surface: (document.getElementById('paywall-checkout') || {}).dataset
1739                                     ? (document.getElementById('paywall-checkout').dataset.upgradeCta || 'paywall')
1740                                     : 'paywall'
1741                        }})
1742                    });
1743                } catch (_) {}
1744            });
1745        })();
1746
1747        // ------------------------------------------------------------------
1748        // Drive the custom scrollbar. Native ones are overlay-only on macOS/iOS,
1749        // so there is nothing to style — this reflects scroll position directly.
1750        // ------------------------------------------------------------------
1751        (function () {
1752            const sc = document.getElementById('paywall-scroll');
1753            const bar = document.getElementById('paywall-sb');
1754            const thumb = document.getElementById('paywall-sb-thumb');
1755            if (!sc || !bar || !thumb) return;
1756
1757            function sync() {
1758                const overflow = sc.scrollHeight - sc.clientHeight;
1759                if (overflow <= 2) { bar.style.opacity = '0'; return; }
1760                bar.style.opacity = '1';
1761                const trackH = bar.clientHeight;
1762                // Floor the thumb so it stays grabbable on very long forms.
1763                const thumbH = Math.max(28, trackH * (sc.clientHeight / sc.scrollHeight));
1764                const maxTravel = trackH - thumbH;
1765                thumb.style.height = thumbH + 'px';
1766                thumb.style.transform =
1767                    'translateY(' + (maxTravel * (sc.scrollTop / overflow)) + 'px)';
1768            }
1769
1770            sc.addEventListener('scroll', sync, {passive: true});
1771            window.addEventListener('resize', sync);
1772            // Stripe mounts asynchronously and then resizes its iframe, so the
1773            // content height changes after load — watch for it rather than
1774            // measuring once.
1775            if (window.ResizeObserver) {
1776                new ResizeObserver(sync).observe(sc.firstElementChild ? sc : sc);
1777                const mount = document.getElementById('paywall-checkout');
1778                if (mount) new ResizeObserver(sync).observe(mount);
1779            }
1780            setTimeout(sync, 500); setTimeout(sync, 2000); setTimeout(sync, 5000);
1781        })();
1782
1783        // Reset modal content when it's closed
1784        if (checkoutModal) {
1785            checkoutModal.addEventListener('close', () => {
1786                if (checkout) {
1787                    checkout.destroy();
1788                    checkout = null;
1789                }
1790                const checkoutDiv = document.getElementById('checkout');
1791                if (checkoutDiv) {
1792                    checkoutDiv.innerHTML = '<div class="flex justify-center items-center p-8"><span class="loading loading-dots loading-lg"></span></div>';
1793                }
1794            });
1795        }
1796    })();
1797</script>
1797
1798
1799
1800<!-- Logic to trigger the trial/pay modals if limits are hit -->
1801<script>
1802    (function () {
1803        function openAutomodal() {
1804            
1805        }
1806
1807        if (document.readyState === "loading") {
1808            document.addEventListener("DOMContentLoaded", openAutomodal);
1809        } else {
1810            openAutomodal();
1811        }
1812    })();
1813</script>
1813
1814
1815
1816
1817
1818
1819
1820<style>
1821    #transcript-content::-webkit-scrollbar,
1822    #ai-section::-webkit-scrollbar {
1823        width: 8px;
1824    }
1825
1826    #transcript-content::-webkit-scrollbar-track,
1827    #ai-section::-webkit-scrollbar-track {
1828        background: #f1f1f1;
1829        border-radius: 10px;
1830    }
1831
1832    #transcript-content::-webkit-scrollbar-thumb,
1833    #ai-section::-webkit-scrollbar-thumb {
1834        background: #d1d5db;
1835        border-radius: 10px;
1836    }
1837
1838    #transcript-content::-webkit-scrollbar-thumb:hover,
1839    #ai-section::-webkit-scrollbar-thumb:hover {
1840        background: #9ca3af;
1841    }
1842
1843    .rotate-180 {
1844        transform: rotate(180deg);
1845    }
1846
1847    .pinned {
1848        position: fixed;
1849        top: 1rem;
1850        width: 55%;
1851        z-index: 50;
1852    }
1853
1854    /* STYLES FOR GENERATED CONTENT SCROLL & HEIGHT LIMIT */
1855    .content-area {
1856        max-height: 75vh;
1857        overflow-y: auto;
1858        scrollbar-width: thin;
1859        scrollbar-color: #d1d5db #f1f1f1;
1860    }
1861
1862    .content-area::-webkit-scrollbar {
1863        width: 8px;
1864    }
1865
1866    .content-area::-webkit-scrollbar-track {
1867        background: #f1f1f1;
1868        border-radius: 10px;
1869    }
1870
1871    .content-area::-webkit-scrollbar-thumb {
1872        background: #d1d5db;
1873        border-radius: 10px;
1874    }
1875
1876    .content-area::-webkit-scrollbar-thumb:hover {
1877        background: #9ca3af;
1878    }
1879
1880    @media (max-width: 767px) {
1881        .content-area {
1882            max-height: 90vh;
1883        }
1884    }
1885</style>
1886
1887
1888
1889<script>
1890    const AI_FEATURES = {"ai_content_checker": {"credit_cost": 1, "display_name": "AI Checker", "is_premium": true, "prompt": "Analyze the text and provide an assessment of the likelihood that it was generated by AI."}, "blog_article": {"credit_cost": 5, "display_name": "Blog Article", "is_premium": true, "prompt": "Write a short blog article based on the provided transcript, with a title, introduction, body, and conclusion."}, "blog_outline": {"credit_cost": 2, "display_name": "Blog Outline", "is_premium": false, "prompt": "Generate a structured outline for a blog post based on the transcript."}, "bullet_points": {"credit_cost": 1, "display_name": "Bullet Points", "is_premium": false, "prompt": "Extract the key points from the following transcript and present them as a bulleted list."}, "charting_method": {"credit_cost": 3, "display_name": "Charting Method", "is_premium": true, "prompt": "If applicable, organize the information from the transcript into a chart or table format for easy comparison of facts."}, "clean_transcript": {"credit_cost": 1, "display_name": "Clean Transcript", "is_premium": false, "prompt": "Please clean up the following transcript by removing filler words (like \u0027um\u0027, \u0027uh\u0027, \u0027you know\u0027), correcting obvious grammar mistakes, and structuring it into clear paragraphs. Do not summarize, just clean the existing text."}, "concept_map": {"credit_cost": 3, "display_name": "Concept Map", "is_premium": true, "prompt": "Generate a concept map in a hierarchical list format, showing the main topics and their relationships from the transcript."}, "cornell_notes": {"credit_cost": 3, "display_name": "Cornell Notes", "is_premium": true, "prompt": "Format the key points of the transcript into the Cornell note-taking system, with sections for cues, notes, and a summary."}, "detailed_summary": {"credit_cost": 2, "display_name": "Detailed Summary", "is_premium": false, "prompt": "Provide a detailed, multi-paragraph summary of the following transcript, covering all main topics and arguments."}, "extract_ideas": {"credit_cost": 2, "display_name": "Extract Ideas", "is_premium": true, "prompt": "Brainstorm and extract creative ideas, concepts, and potential projects mentioned in the transcript."}, "extract_insights": {"credit_cost": 2, "display_name": "Extract Insights", "is_premium": true, "prompt": "Analyze the transcript to find deeper meanings, non-obvious connections, and key insights."}, "extract_main_idea": {"credit_cost": 2, "display_name": "Main Idea", "is_premium": true, "prompt": "Distill the entire transcript down to its single most important core message or main idea."}, "extract_patterns": {"credit_cost": 2, "display_name": "Extract Patterns", "is_premium": true, "prompt": "Identify and describe any recurring themes, arguments, or patterns in the transcript."}, "extract_predictions": {"credit_cost": 2, "display_name": "Extract Predictions", "is_premium": true, "prompt": "Identify and list any future predictions or forecasts made in the transcript."}, "extract_references": {"credit_cost": 2, "display_name": "Extract References", "is_premium": true, "prompt": "Extract any mentioned books, articles, tools, or other references from the transcript."}, "extract_tweet_ideas": {"credit_cost": 2, "display_name": "Extract Tweet Ideas", "is_premium": false, "prompt": "Extract several tweetable moments or quotes from the transcript."}, "extract_wisdom": {"credit_cost": 2, "display_name": "Extract Wisdom", "is_premium": true, "prompt": "Extract the most valuable wisdom, life lessons, and actionable advice from the transcript."}, "key_insights": {"credit_cost": 2, "display_name": "Key Insights", "is_premium": true, "prompt": "Analyze the following transcript and extract the most important insights and takeaways as a bulleted list."}, "linkedin_article_idea": {"credit_cost": 2, "display_name": "LinkedIn Article Idea", "is_premium": true, "prompt": "Create an outline for a professional LinkedIn article based on the transcript."}, "linkedin_post": {"credit_cost": 2, "display_name": "LinkedIn Post", "is_premium": true, "prompt": "Write a professional LinkedIn post summarizing the key takeaways from the transcript."}, "micro_summary": {"credit_cost": 1, "display_name": "Micro Summary", "is_premium": false, "prompt": "Summarize the following transcript in 1-2 concise sentences."}, "notable_quotes": {"credit_cost": 1, "display_name": "Notable Quotes", "is_premium": false, "prompt": "Identify and list several of the most notable or memorable quotes from the transcript."}, "outline_notes": {"credit_cost": 2, "display_name": "Outline Notes", "is_premium": false, "prompt": "Create a structured outline of the transcript, using headings and nested bullet points to organize the information."}, "proper_notes_generation": {"credit_cost": 4, "display_name": "Proper Notes", "is_premium": true, "prompt": "Generate a comprehensive and well-structured set of notes from the transcript, suitable for study or research."}, "qa_split_page": {"credit_cost": 2, "display_name": "Q\u0026A Split Page", "is_premium": true, "prompt": "Create a two-column layout with questions on the left and answers from the transcript on the right."}, "qec_method": {"credit_cost": 3, "display_name": "QEC Method", "is_premium": true, "prompt": "Structure the transcript\u0027s arguments using the Question-Evidence-Conclusion (QEC) method."}, "questions_answers": {"credit_cost": 2, "display_name": "Q\u0026A", "is_premium": true, "prompt": "Generate a list of questions and their corresponding answers based on the content of the transcript."}, "rapid_logging": {"credit_cost": 2, "display_name": "Rapid Logging", "is_premium": true, "prompt": "Convert the transcript into a rapid-logging format, using short, objective sentences and bullet points."}, "short_summary": {"credit_cost": 1, "display_name": "Short Summary", "is_premium": false, "prompt": "Provide a brief, single-paragraph summary of the following transcript."}, "speaker_identification": {"credit_cost": 4, "display_name": "Speaker ID", "is_premium": true, "prompt": "Analyze the transcript and attempt to label different speakers (e.g., Speaker 1, Speaker 2). This may not be perfectly accurate."}, "study_flashcards": {"credit_cost": 3, "display_name": "Flashcards", "is_premium": true, "prompt": "Create a list of flashcards (question/answer format) based on the key information in the transcript for studying."}, "tnote_method": {"credit_cost": 3, "display_name": "T-Note Method", "is_premium": true, "prompt": "Organize the transcript\u0027s information using the T-Note method, focusing on main concepts, definitions, and supporting details."}, "twitter_post_ideas": {"credit_cost": 2, "display_name": "Twitter Post Ideas", "is_premium": false, "prompt": "Generate 5 engaging tweet ideas based on the content of the transcript."}, "twitter_thread": {"credit_cost": 4, "display_name": "Twitter Thread (Full)", "is_premium": true, "prompt": "Generate a full Twitter thread with multiple tweets based on the transcript, complete with hooks and summaries."}, "twitter_thread_idea": {"credit_cost": 3, "display_name": "Twitter Thread Idea", "is_premium": true, "prompt": "Outline a compelling Twitter thread (3-5 tweets) based on a key topic from the transcript."}, "viral_clip_screener": {"credit_cost": 5, "display_name": "Viral Clip Screener", "is_premium": true, "prompt": "Imagine you are a social media manager. Identify 3-5 moments from this transcript that have the highest potential to become a viral short
1890-form video (like a TikTok or Reel). For each moment, describe the scene or quote and explain why it\u0027s compelling."}};
1891
1892    // YouTube IFrame API Logic
1893    // YouTube IFrame API Logic
1894    let player;
1895    window.onYouTubeIframeAPIReady = function () {
1896        console.log("YouTube API Ready, initializing player...");
1897        player = new YT.Player('youtube-player', {
1898            height: '100%',
1899            width: '100%',
1900            videoId: '',
1901            playerVars: {
1902                'playsinline': 1,
1903                'rel': 0,
1904                'modestbranding': 1,
1905                'enablejsapi': 1,
1906                'autoplay': 1
1907            },
1908            events: {
1909                'onReady': onPlayerReady
1910            }
1911        });
1912    };
1913
1914    function onPlayerReady(event) {
1915        console.log("YouTube Player Ready event fired");
1916    }
1917
1918    window.seekToTime = function (seconds) {
1919        const secs = parseFloat(seconds);
1920        console.log("Seeking to:", secs);
1921        if (player && typeof player.seekTo === 'function') {
1922            player.seekTo(secs, true);
1923            player.playVideo();
1924        } else {
1925            console.warn("Player not fully ready, falling back to iframe src update");
1926            const iframe = document.getElementById('youtube-player');
1927            if (iframe) {
1928                const baseUrl = `https://www.youtube.com/embed/`;
1929                const params = `?start=${Math.floor(secs)}&autoplay=1&enablejsapi=1`;
1930                iframe.src = baseUrl + params;
1931            } else {
1932                console.error("Could not find youtube-player element for seeking fallback");
1933            }
1934        }
1935    };
1936
1937    // CLEAR CONTENT FUNCTION
1938    function clearAllGeneratedContent() {
1939        const container = document.querySelector('.generated-content-container');
1940        const emptyState = document.querySelector('.generated-content-empty');
1941        const clearBtn = document.getElementById('clear-all-content-btn');
1942
1943        // Clear all child nodes (generated cards)
1944        while (container.firstChild) {
1945            container.removeChild(container.firstChild);
1946        }
1947
1948        // Append the empty/upsell state and ensure it's visible
1949        container.appendChild(emptyState);
1950        emptyState.style.display = 'flex'; // Use flex to match its inner layout
1951
1952        // Hide the clear button
1953        clearBtn.classList.add('hidden');
1954
1955        // Adjust column heights after clearing
1956        if (window.adjustColumnHeights) {
1957            window.adjustColumnHeights();
1958        }
1959    }
1960
1961    // GLOBAL STATE
1962    let IS_AUTHENTICATED = document.body.dataset.authenticated === 'true';
1963    // Paying users only — a free registered account is not premium.
1964    const IS_PREMIUM = false;
1965
1966    const authModal = document.getElementById('auth_modal');
1967    const transcriptCheckoutModal = document.getElementById('transcript_checkout_modal');
1968    const confirmEmailModal = document.getElementById('confirm_email_modal');
1969    const premiumRequiredModal = document.getElementById('premium_required_modal');
1970    const upgradeFromConfirmBtn = document.getElementById('upgrade-from-confirm-btn');
1971    const upgradeFromPremiumModalBtn = document.getElementById('upgrade-from-premium-modal-btn');
1972    const resendConfirmBtn = document.getElementById('resend-confirm-btn');
1973    const stripe = Stripe("pk_live_51GYV9XIHI17dZ9P7DQ88NMUhg7MBvq3VIhBNOxMw1v4a3HOPk0N92IdHXeHvsnQO48BAdyFA2fTtJewjf5pqyabu0071HXg26E");
1974    let transcriptCheckout = null;
1975
1976    const toggleBtn = document.getElementById('toggle-timestamps-btn');
1977    const copyBtn = document.getElementById('copy-btn');
1978    const transcriptContent = document.getElementById('transcript-content');
1979    const mostUsedContainer = document.querySelector('.most-used-list');
1980    const mostUsedEmpty = document.querySelector('.most-used-empty');
1981
1982    // ELEMENT REFERENCES
1983    const clearBtn = document.getElementById('clear-all-content-btn');
1984    const scrollToAiBtn = document.getElementById('scroll-to-ai-btn');
1985    const aiSection = document.getElementById('ai-section');
1986
1987    // --- NEW: LOGIC FOR FREE TRIAL BUTTON IN MODAL ---
1988    const trialBtn = document.getElementById('trigger-trial-checkout');
1989
1990    if (trialBtn) {
1991        trialBtn.addEventListener('click', async () => {
1992            // 1. Close the "Limit Reached" modal
1993            const trialModal = document.getElementById('free_trial_modal');
1994            if (trialModal) trialModal.close();
1995
1996            // 2. Open the Checkout Modal (Loading State)
1997            if (transcriptCheckoutModal) transcriptCheckoutModal.showModal();
1998
1999            try {
2000                // 4. Call Backend
2001                const response = await fetch('/create-checkout-session', {
2002                    method: 'POST',
2003                    headers: { 'Content-Type': 'application/json' },
2004                    body: JSON.stringify({
2005                        planName: 'premium_yearly',
2006                        returnUrl: window.location.href,
2007                        isTrial: true
2008                    })
2009                });
2010
2011                const { clientSecret, fallbackUrl, error } = await response.json();
2012
2013                if (error || !clientSecret) {
2014                    if (fallbackUrl) { window.location.href = fallbackUrl; return; }
2015                    console.error('Server error:', error);
2016                    document.getElementById('transcript-checkout').innerHTML = `<p class="text-red-500 text-center">Could not initialize payment. Please try again.</p>`;
2017                    return;
2018                }
2019
2020                // 5. Mount Stripe — embedded with hosted fallback for ETP
2021                if (transcriptCheckout) {
2022                    transcriptCheckout.destroy();
2023                }
2024                try {
2025                    transcriptCheckout = await stripe.initEmbeddedCheckout({ clientSecret });
2026                    document.getElementById('transcript-checkout').innerHTML = '';
2027                    transcriptCheckout.mount('#transcript-checkout');
2028                } catch (innerE) {
2029                    console.warn('Embedded init failed, falling back to hosted:', innerE);
2030                    if (fallbackUrl) window.location.href = fallbackUrl;
2031                    else throw innerE;
2032                }
2033
2034            } catch (e) {
2035                console.error("Fetch error:", e);
2036                document.getElementById('transcript-checkout').innerHTML = `<p class="text-red-500 text-center">Connection failed.</p>`;
2037            }
2038        });
2039    }
2040    // --- END NEW LOGIC ---
2041
2042    // Define showUpgradeModal in a scope accessible by onclick attributes.
2043    // Behavior matches premium.html + audio_viewer.html (Batch B):
2044    //   - reads BOTH clientSecret + fallbackUrl from server
2045    //   - falls back to hosted Stripe URL if embedded init throws
2046    //     (Firefox ETP / Brave / Safari ITP block m.stripe.network)
2047    window.showUpgradeModal = async function (isTrial = false) {
2048        // --- 1. GUEST CHECK: Redirect to Register if not logged in ---
2049        if (!IS_AUTHENTICATED) {
2050            window.location.href = "/register?intent=trial";
2051            return;
2052        }
2053        // -------------------------------------------------------------
2054
2055        if (!transcriptCheckoutModal) return;
2056        transcriptCheckoutModal.showModal();
2057
2058        let payload;
2059        try {
2060            const response = await fetch('/create-checkout-session', {
2061                method: 'POST',
2062                headers: { 'Content-Type': 'application/json' },
2063                body: JSON.stringify({
2064                    planName: 'premium_monthly',
2065                    returnUrl: window.location.href,
2066                    isTrial: isTrial
2067                })
2068            });
2069            payload = await response.json();
2070        } catch (e) {
2071            document.getElementById('transcript-checkout').innerHTML = `<p class="text-red-500 text-center">Network error contacting our server.</p>`;
2072            return;
2073        }
2074
2075        const { clientSecret, fallbackUrl, error } = payload;
2076
2077        if (error || !clientSecret) {
2078            if (fallbackUrl) { window.location.href = fallbackUrl; return; }
2079            console.error('Server error:', error);
2080            document.getElementById('transcript-checkout').innerHTML = `<p class="text-red-500 text-center">Could not initialize payment. Please try again.</p>`;
2081            return;
2082        }
2083
2084        if (transcriptCheckout) {
2085            transcriptCheckout.destroy();
2086            transcriptCheckout = null;
2087        }
2088        try {
2089            transcriptCheckout = await stripe.initEmbeddedCheckout({ clientSecret });
2090            document.getElementById('transcript-checkout').innerHTML = '';
2091            transcriptCheckout.mount('#transcript-checkout');
2092        } catch (e) {
2093            // Embedded blocked (tracking-protection) — graceful fallback.
2094            console.warn('Embedded init failed, falling back to hosted:', e);
2095            if (fallbackUrl) {
2096                window.location.href = fallbackUrl;
2097            } else {
2098                document.getElementById('transcript-checkout').innerHTML = `<p class="text-red-500 text-center">Could not load checkout. ${e.message || ''}</p>`;
2099            }
2100        }
2101    }
2102
2103    function updateMostUsedFeatures() {
2104        if (!mostUsedContainer || !mostUsedEmpty || !IS_AUTHENTICATED) return;
2105
2106        const usageCount = JSON.parse(localStorage.getItem('featureUsageCount')) || {};
2107        const sortedFeatures = Object.entries(usageCount)
2108            .sort(([, a], [, b]) => b - a)
2109            .slice(0, 5);
2110
2111        if (sortedFeatures.length === 0) {
2112            mostUsedContainer.classList.add('hidden');
2113            mostUsedEmpty.classList.remove('hidden');
2114            return;
2115        }
2116
2117        const buttonContainer = mostUsedContainer.querySelector('.flex-wrap');
2118        buttonContainer.innerHTML = ''; // Clear existing buttons
2119
2120        for (const [featureType, count] of sortedFeatures) {
2121            const featureDetails = AI_FEATURES[featureType];
2122            if (featureDetails) {
2123                const button = document.createElement('button');
2124                button.className = 'btn btn-sm btn-ghost ai-preset-btn';
2125                button.dataset.generationType = featureType;
2126                button.innerHTML = `<i class="ph-duotone ph-sparkle"></i> ${featureDetails.display_name}`;
2127                button.addEventListener('click', handleAiFeatureClick);
2128                buttonContainer.appendChild(button);
2129            }
2130        }
2131
2132        mostUsedContainer.classList.remove('hidden');
2133        mostUsedEmpty.classList.add('hidden');
2134    }
2135
2136    if (toggleBtn && transcriptContent) {
2137        toggleBtn.addEventListener('click', function () {
2138            const timestampsOn = this.dataset.timestampsOn === 'true';
2139            const timestamps = transcriptContent.querySelectorAll('.transcript-timestamp');
2140
2141            if (timestampsOn) {
2142                timestamps.forEach(ts => ts.style.display = 'none');
2143                this.dataset.timestampsOn = 'false';
2144                this.classList.add('btn-outline');
2145            } else {
2146                timestamps.forEach(ts => ts.style.display = 'inline');
2147                this.dataset.timestampsOn = 'true';
2148                this.classList.remove('btn-outline');
2149            }
2150        });
2151    }
2152
2153    if (copyBtn && transcriptContent) {
2154        copyBtn.addEventListener('click', function () {
2155            const timestampsOn = toggleBtn ? toggleBtn.dataset.timestampsOn === 'true' : true;
2156            let textToCopy = '';
2157
2158            transcriptContent.querySelectorAll('p').forEach(p => {
2159                let lineText = '';
2160                if (timestampsOn) {
2161                    const timestampEl = p.querySelector('.transcript-timestamp');
2162                    if (timestampEl && window.getComputedStyle(timestampEl).display !== 'none') {
2163                        lineText += timestampEl.innerText.trim() + ' ';
2164                    }
2165                }
2166                const textEl = p.querySelector('.transcript-text');
2167                if (textEl) {
2168                    lineText += textEl.innerText.trim();
2169                }
2170                textToCopy += lineText + '\n';
2171            });
2172
2173            navigator.clipboard.writeText(textToCopy.trim()).then(() => {
2174                const originalText = copyBtn.innerHTML;
2175                copyBtn.innerHTML = '<i class="ph-duotone ph-check-circle text-[16px]"></i> Copied!';
2176                setTimeout(() => {
2177                    copyBtn.innerHTML = originalText;
2178                }, 2000);
2179
2180                // A successful copy is the best-timed offer on this page: they
2181                // came for the transcript, they have it, and they are about to
2182                // leave. 89% of visitors take exactly one transcript and never
2183                // return, so this is the only moment most of them are ever
2184                // reachable — the daily limit is 1, so they never hit a paywall.
2185                //
2186                // Delayed so the "Copied!" confirmation lands first; showing it
2187                // instantly would read as a punishment for copying. Once per
2188                // page view, and never for paying users.
2189                if (!IS_PREMIUM && !window.__copyOfferShown) {
2190                    window.__copyOfferShown = true;
2191                    setTimeout(function () {
2192                        try {
2193                            fetch('/api/track', {
2194                                method: 'POST', credentials: 'same-origin',
2195                                headers: {'Content-Type': 'application/json'},
2196                                body: JSON.stringify({event: 'copy_offer_shown', meta: {
2197                                    audience: IS_AUTHENTICATED ? 'member' : 'guest'
2198                                }})
2199                            });
2200                        } catch (_) {}
2201                        const m = document.getElementById('pay_prompt_modal');
2202                        // Guests see "Create Free Account" here, and it was a bare
2203                        // /register: anyone who signed up from it lost this
2204                        // transcript (register sends a no-next signup to /premium),
2205                        // and the signup could not be told apart from any other.
2206                        // Give it a return address and tag the source; SIGNUP rows
2207                        // record the path, so copy signups are countable on their
2208                        // own (details LIKE '%from=copy%'), separately from the
2209                        // export gate's ?export=. Restored on close so a later
2210                        // trigger of the same modal is not credited to copy.
2211                        const signup = document.getElementById('pay-prompt-signup');
2212                        if (m && signup && !IS_AUTHENTICATED) {
2213                            const plain = signup.getAttribute('href');
2214                            signup.setAttribute('href', plain + (plain.indexOf('?') === -1 ? '?' : '&') +
2215                                'next=' + encodeURIComponent(window.location.pathname + '?from=copy'));
2216                            m.addEventListener('close', function () { signup.setAttribute('href', plain); }, { once: true });
2217                        }
2218                        if (m) { try { m.showModal(); } catch (_) { m.setAttribute('open', ''); } }
2219                    }, 1800);
2220                }
2221            }).catch(err => {
2222                console.error('Failed to copy text: ', err);
2223            });
2224        });
2225    }
2226
2227    const leftColumn = document.querySelector('.w-full.md\\:w-1\\/2.lg\\:w-3\\/5');
2228    const rightColumn = document.querySelector('.w-full.md\\:w-1\\/2.lg\\:w-2\\/5');
2229    const goToTopBtn = document.getElementById('go-to-top-btn');
2230    const pinBtn = document.getElementById('pin-btn');
2231    const videoContainer = document.getElementById('video');
2232
2233    if (pinBtn) {
2234        pinBtn.addEventListener('click', () => {
2235            videoContainer.classList.toggle('pinned');
2236        });
2237    }
2238
2239    window.adjustColumnHeights = function () {
2240        if (!leftColumn || !rightColumn) return;
2241        if (window.innerWidth >= 768) {
2242            leftColumn.style.height = 'auto';
2243            rightColumn.style.height = 'auto';
2244            const leftHeight = leftColumn.offsetHeight;
2245            const rightHeight = rightColumn.offsetHeight;
2246            if (rightHeight > leftHeight) {
2247                leftColumn.style.height = `${rightHeight}px`;
2248            }
2249        } else {
2250            leftColumn.style.height = 'auto';
2251            rightColumn.style.height = 'auto';
2252        }
2253    }
2254
2255    if (goToTopBtn) {
2256        goToTopBtn.addEventListener('click', function () {
2257            const videoElement = document.getElementById('video');
2258            if (videoElement) {
2259                videoElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
2260            }
2261        });
2262    }
2263
2264    adjustColumnHeights();
2265    const observer = new MutationObserver(adjustColumnHeights);
2266    if (rightColumn) {
2267        observer.observe(rightColumn, { childList: true, subtree: true });
2268    }
2269    window.addEventListener('resize', adjustColumnHeights);
2270
2271    document.querySelectorAll('.category-header').forEach(header => {
2272        header.addEventListener('click', () => {
2273            const content = header.nextElementSibling;
2274            const icon = header.querySelector('i.ph-caret-down');
2275
2276            content.classList.toggle('hidden');
2277            icon.classList.toggle('rotate-180');
2278
2279            setTimeout(adjustColumnHeights, 300);
2280        });
2281    });
2282
2283    document.querySelectorAll('.ai-preset-btn, .feature-btn').forEach(button => {
2284        button.addEventListener('click', handleAiFeatureClick);
2285    });
2286
2287    async function handleAiFeatureClick(event) {
2288        if (!IS_AUTHENTICATED) {
2289            if (authModal) authModal.showModal();
2290            return;
2291        }
2292
2293        const button = event.currentTarget;
2294        const generationType = button.dataset.generationType;
2295        if (!generationType) return;
2296
2297        const featureTitle = AI_FEATURES[generationType]?.display_name || generationType.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
2298        const languageSelector = document.querySelector('.ai-language-picker');
2299        const selectedLanguage = languageSelector ? languageSelector.value : 'English';
2300
2301        const usageCount = JSON.parse(localStorage.getItem('featureUsageCount')) || {};
2302        usageCount[generationType] = (usageCount[generationType] || 0) + 1;
2303        localStorage.setItem('featureUsageCount', JSON.stringify(usageCount));
2304        updateMostUsedFeatures();
2305
2306        let fullTranscript = '';
2307        document.querySelectorAll('.transcript-text').forEach(span => {
2308            fullTranscript += span.innerText.trim() + ' ';
2309        });
2310
2311        if (fullTranscript.trim().length < 20) {
2312            alert("Transcript is too short to process.");
2313            return;
2314        }
2315
2316        const generatedContentContainer = document.querySelector('.generated-content-container');
2317        const emptyState = document.querySelector('.generated-content-empty');
2318        if (emptyState) emptyState.style.display = 'none';
2319        if (clearBtn) clearBtn.classList.remove('hidden'); // Show clear button
2320
2321        const loadingCard = createContentCard('Generating...', '<div class="flex justify-center items-center p-4"><span class="loading loading-dots loading-lg"></span></div>', 'loading-card');
2322        generatedContentContainer.prepend(loadingCard);
2323        adjustColumnHeights();
2324
2325        try {
2326            const response = await fetch('/api/generate', {
2327                method: 'POST',
2328                headers: { 'Content-Type': 'application/json' },
2329                body: JSON.stringify({
2330                    transcript: fullTranscript,
2331                    feature: generationType,
2332                    video_id: '',
2333                    language: selectedLanguage
2334                }),
2335            });
2336
2337            document.getElementById('loading-card')?.remove();
2338            const result = await response.json();
2339
2340            if (!response.ok) {
2341                if (response.status === 403 && result.errorCode === 'EMAIL_NOT_CONFIRMED') {
2342                    if (confirmEmailModal) confirmEmailModal.showModal();
2343                } else if (response.status === 403 && result.errorCode === 'PREMIUM_FEATURE_LOCKED') {
2344                    const featureNameSpan = document.getElementById('premium-feature-name');
2345                    if (featureNameSpan) featureNameSpan.textContent = featureTitle;
2346                    if (premiumRequiredModal) premiumRequiredModal.showModal();
2347
2348                    const upgradeLink = `<a href="javascript:void(0)" onclick="window.showUpgradeModal()" class="link link-primary">upgrade your plan</a>`;
2349                    const errorMessage = `This is a premium feature. Please ${upgradeLink} to use it.`;
2350                    const errorCard = createContentCard(`Error: ${featureTitle}`, errorMessage);
2351                    generatedContentContainer.prepend(errorCard);
2352
2353                } else if (response.status === 429) {
2354                    await window.showUpgradeModal();
2355                } else {
2356                    const errorCard = createContentCard('Error', `<p class="text-red-500">${result.error || 'An unknown error occurred.'}</p>`);
2357                    generatedContentContainer.prepend(errorCard);
2358                }
2359            } else {
2360                const resultCard = createContentCard(featureTitle, result.content);
2361                generatedContentContainer.prepend(resultCard);
2362            }
2363        } catch (error) {
2364            document.getElementById('loading-card')?.remove();
2365            const errorCard = createContentCard('Error', `<p class="text-red-500">Failed to connect to the server. Please try again.</p>`);
2366            generatedContentContainer.prepend(errorCard);
2367            console.error('API call failed:', error);
2368        } finally {
2369            adjustColumnHeights();
2370        }
2371    }
2372
2373    if (transcriptCheckoutModal) {
2374        transcriptCheckoutModal.addEventListener('close', () => {
2375            if (transcriptCheckout) {
2376                transcriptCheckout.destroy();
2377                transcriptCheckout = null;
2378            }
2379            document.getElementById('transcript-checkout').innerHTML = '<div class="flex justify-center items-center p-8"><span class="loading loading-dots loading-lg"></span></div>';
2380        });
2381    }
2382
2383    if (upgradeFromConfirmBtn && confirmEmailModal) {
2384        upgradeFromConfirmBtn.addEventListener('click', () => {
2385            confirmEmailModal.close();
2386            window.showUpgradeModal();
2387        });
2388    }
2389
2390    if (upgradeFromPremiumModalBtn && premiumRequiredModal) {
2391        upgradeFromPremiumModalBtn.addEventListener('click', () => {
2392            premiumRequiredModal.close();
2393            window.showUpgradeModal();
2394        });
2395    }
2396
2397    if (resendConfirmBtn) {
2398        resendConfirmBtn.addEventListener('click', async () => {
2399            const btnText = resendConfirmBtn.querySelector('.btn-text');
2400            const btnLoader = resendConfirmBtn.querySelector('.btn-loader');
2401            const messageDiv = document.getElementById('resend-message');
2402
2403            resendConfirmBtn.disabled = true;
2404            btnText.textContent = 'Sending...';
2405            btnLoader.classList.remove('hidden');
2406            messageDiv.innerText = '';
2407
2408            try {
2409                const response = await fetch('/resend-confirmation', { method: 'POST' });
2410                const result = await response.json();
2411
2412                if (response.ok) {
2413                    messageDiv.innerText = result.message;
2414                    messageDiv.classList.remove('text-red-600');
2415                    messageDiv.classList.add('text-green-600');
2416                } else {
2417                    messageDiv.innerText = result.error || 'An error occurred.';
2418                    messageDiv.classList.remove('text-green-600');
2419                    messageDiv.classList.add('text-red-600');
2420                }
2421            } catch (e) {
2422                messageDiv.innerText = 'Failed to connect to server.';
2423                messageDiv.classList.remove('text-green-600');
2424                messageDiv.classList.add('text-red-600');
2425            } finally {
2426                setTimeout(() => {
2427                    resendConfirmBtn.disabled = false;
2428                    btnText.textContent = 'Resend confirmation email';
2429                    btnLoader.classList.add('hidden');
2430                }, 5000);
2431            }
2432        });
2433    }
2434
2435    // SCROLL TO AI BUTTON LOGIC
2436    if (scrollToAiBtn && typeof aiColumnContainer !== 'undefined') {
2437        window.addEventListener('scroll', () => {
2438            const aiRect = aiColumnContainer.getBoundingClientRect();
2439            if (aiRect.bottom < 0) {
2440                scrollToAiBtn.classList.remove('hidden');
2441            } else {
2442                scrollToAiBtn.classList.add('hidden');
2443            }
2444        });
2445
2446        scrollToAiBtn.addEventListener('click', () => {
2447            aiColumnContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
2448        });
2449    }
2450
2451    function createContentCard(title, content, elementId = null) {
2452        const card = document.createElement('div');
2453        card.className = 'bg-white/80 rounded-lg p-4 border border-blue-200 shadow-sm';
2454        if (elementId) card.id = elementId;
2455        card.innerHTML = `
2456                <div class="flex justify-between items-center mb-2">
2457                    <h4 class="font-bold text-gray-800">${title}</h4>
2458                    <button class="btn btn-xs btn-ghost" onclick="copyCardContent(this)">
2459                        <i class="ph-duotone ph-copy"></i> Copy
2460                    </button>
2461                </div>
2462                <div class="prose prose-sm max-w-none text-gray-700 content-area">${marked.parse(content)}</div>
2463            `;
2464        return card;
2465    }
2466
2467    updateMostUsedFeatures();
2468
2469    // --- TRANSCRIPT SEARCH LOGIC ---
2470    const searchInput = document.getElementById('transcript-search');
2471    if (searchInput) {
2472        searchInput.addEventListener('input', function (e) {
2473            const term = e.target.value.toLowerCase();
2474            const lines = document.querySelectorAll('.transcript-line');
2475
2476            lines.forEach(line => {
2477                const text = line.querySelector('.transcript-text').textContent.toLowerCase();
2478                if (text.includes(term)) {
2479                    line.classList.remove('hidden');
2480                    line.style.backgroundColor = term.length > 0 ? '#eff6ff' : 'transparent';
2481                } else {
2482                    line.classList.add('hidden');
2483                }
2484            });
2485        });
2486    }
2487
2488    // Load YouTube API
2489    const tag = document.createElement('script');
2490    tag.src = "https://www.youtube.com/iframe_api";
2491    const firstScriptTag = document.getElementsByTagName('script')[0];
2492    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
2493
2494    function copyCardContent(button) {
2495        const contentArea = button.closest('.bg-white\\/80').querySelector('.content-area');
2496        navigator.clipboard.writeText(contentArea.innerText).then(() => {
2497            button.innerHTML = '<i class="ph-duotone ph-check-circle"></i> Copied!';
2498            setTimeout(() => {
2499                button.innerHTML = '<i class="ph-duotone ph-copy"></i> Copy';
2500            }, 2000);
2501        });
2502    }
2503
2504    // transcript_viewer.html script section
2505
2506    function fetchDownloadFormats() {
2507        if (!IS_AUTHENTICATED) {
2508            const authModal = document.getElementById('auth_modal');
2509            if (authModal && typeof authModal.showModal === 'function') {
2510                authModal.showModal();
2511            } else {
2512                console.error("Auth modal not found or showModal not supported");
2513            }
2514            return;
2515        }
2516
2517
2518        const modal = document.getElementById('download_modal');
2519        const loader = document.getElementById('download_loader');
2520        const optionsDiv = document.getElementById('download_options');
2521        const headerText = document.getElementById('download_header_text');
2522
2523        if (modal) modal.showModal();
2524        if (loader) loader.classList.remove('hidden');
2525        if (headerText) headerText.classList.remove('hidden');
2526        if (optionsDiv) optionsDiv.innerHTML = '';
2527
2528        const videoId = "";
2529        const videoUrl = "https://www.youtube.com/watch?v=" + videoId;
2530
2531        fetch('/api/download/check-formats', {
2532            method: 'POST',
2533            headers: { 'Content-Type': 'application/json' },
2534            body: JSON.stringify({
2535                url: videoUrl,
2536                video_id: videoId
2537            })
2538        })
2539            .then(async response => {
2540                const data = await response.json();
2541                if (loader) loader.classList.add('hidden');
2542
2543                // 1. Handle Insufficient Credits (Upsell)
2544                if (response.status === 402) {
2545                    // Close the current download modal first
2546                    modal.close();
2547
2548                    // Trigger the Stripe checkout modal (Same as AI features)
2549                    // Pass 'false' because downloads don't usually have a 'trial' version
2550                    window.showUpgradeModal(false);
2551                    return;
2552                }
2553
2554                // if (response.status === 402) {
2555                //    headerText.classList.add('hidden');
2556                //    optionsDiv.innerHTML = `
2557                //        <div class="text-center py-6">
2558                //            <div class="bg-amber-50 border border-amber-200 p-6 rounded-xl">
2559                //                <i class="ph-duotone ph-lock-key text-5xl text-amber-500 mb-3"></i>
2560                //                <h4 class="text-xl font-bold text-gray-800">Insufficient Credits</h4>
2561                //                <p class="text-gray-600 mb-6">You need 2 credits to extract download links. You currently have ${data.current_credits} credits.</p>
2562                //               <a href="/premium" class="btn btn-primary btn-wide">
2563                //                   <i class="ph-duotone ph-rocket"></i> Upgrade or Buy Credits
2564                //               </a>
2565                //            </div>
2566                //        </div>
2567                //    `;
2568                //    return;
2569                //}
2570
2571                if (!response.ok || data.status === 'error') {
2572                    optionsDiv.innerHTML = `<div class="alert alert-error text-sm">Error: ${data.details || "Failed to fetch formats"}</div>`;
2573                    return;
2574                }
2575
2576                // 2. Success: Render the Table with all formats
2577                let html = `
2578	            <div class="alert alert-success py-2 text-xs mb-4">
2579	                <i class="ph ph-check-circle"></i> 2 Credits deducted. Formats extracted successfully.
2580	            </div>
2581	            <table class="table table-xs w-full">
2582	                <thead>
2583	                    <tr class="bg-base-200">
2584	                        <th>Type</th>
2585	                        <th>Resolution/Quality</th>
2586	                        <th>Format</th>
2587	                        <th>Size</th>
2588	                        <th class="text-right">Action</th>
2589	                    </tr>
2590	                </thead>
2591	                <tbody>
2592	        `;
2593
2594                if (data.formats && data.formats.length > 0) {
2595                    data.formats.forEach(f => {
2596                        const badgeClass = f.type.includes('Video + Audio') ? 'badge-success' : (f.type.includes('Audio') ? 'badge-info' : 'badge-warning');
2597
2598                        html += `
2599	                    <tr class="hover">
2600	                        <td><span class="badge ${badgeClass} badge-outline badge-xs text-[10px] whitespace-nowrap">${f.type}</span></td>
2601	                        <td class="font-bold">${f.resolution}</td>
2602	                        <td><span class="badge badge-ghost badge-xs uppercase">${f.ext}</span></td>
2603	                        <td>${f.size}</td>
2604	                        <td class="text-right">
2605	                            <a href="${f.direct_url}" 
2606	                               target="_blank" 
2607	                               rel="noopener noreferrer"
2608	                               class="btn btn-xs btn-warning text-black hover:bg-yellow-400 font-medium whitespace-nowrap">
2609	                                <i class="ph-duotone ph-download-simple"></i> Download
2610	                            </a>
2611	                        </td>
2612	                    </tr>
2613	                `;
2614                    });
2615                } else {
2616                    html += `<tr><td colspan="5" class="text-center py-6 text-gray-500 italic">No formats found.</td></tr>`;
2617                }
2618
2619                html += '</tbody></table>';
2620                optionsDiv.innerHTML = html;
2621
2622                // Update user's visual credit count if you have a credit display on page
2623                const creditDisplay = document.querySelector('.user-credits-count');
2624                if (creditDisplay && data.new_balance !== undefined) {
2625                    creditDisplay.innerText = data.new_balance;
2626                }
2627            })
2628            .catch(err => {
2629                if (loader) loader.classList.add('hidden');
2630                optionsDiv.innerHTML = `<div class="alert alert-error text-sm">Network Error: ${err.message}</div>`;
2631            });
2632    }
2633
2634    // Helper function to handle UI after clicking download
2635    function confirmCharge(cost) {
2636        // Optional: Add a toast notification here if you have a toast library
2637        // Close modal after 1.5 seconds so user sees the download started
2638        setTimeout(() => {
2639            const modal = document.getElementById('download_modal');
2640            if (modal) modal.close();
2641        }, 1500);
2642    }
2643
2644
2645
2646</script>
2646
2647
2648
2649
2650<script>
2651    // Export gate: an account first, not a payment.
2652    //
2653    // On 11 Aug this was switched from "create a free account to export" to
2654    // "pay to export". New accounts fell from ~230/day to ~100/day and trials
2655    // from ~6/day to ~2.4/day, and neither recovered after the 16 Aug revert,
2656    // which left this gate as it was. The trial pipeline is members — all 71
2657    // trials since then were logged-in users, 61 of them via /premium — so a
2658    // gate that stops creating accounts starves it. Meanwhile the pay version
2659    // turned 527 member export clicks into zero trials.
2660    //
2661    // So: guests create an account, and the return address brings them back to
2662    // this transcript with the export they asked for already starting. Free
2663    // members get TXT/PDF/DOCX, as before 11 Aug. SRT/VTT are Pro, except that
2664    // every member gets one free — /api/export enforces both server side.
2665    const EXPORT_PRO_ONLY = ['srt', 'vtt'];
2666    const FREE_SUBTITLE_LEFT = false;
2667
2668    function _trackExportGate(format, action) {
2669        try {
2670            fetch('/api/track', {
2671                method: 'POST', credentials: 'same-origin',
2672                headers: {'Content-Type': 'application/json'},
2673                body: JSON.stringify({event: 'export_gate_hit', meta: {
2674                    format: format,
2675                    audience: IS_AUTHENTICATED ? 'member' : 'guest',
2676                    action: action
2677                }})
2678            });
2679        } catch (_) {}
2680    }
2681
2682    window.handleExportAuth = function (event, format) {
2683        if (IS_PREMIUM) return true;
2684
2685        if (IS_AUTHENTICATED) {
2686            if (EXPORT_PRO_ONLY.indexOf(format) === -1) return true;
2687            if (FREE_SUBTITLE_LEFT) return true;
2688            event.preventDefault();
2689            _trackExportGate(format, 'paywall');
2690            // Someone clicking SRT/VTT has said exactly what they want and why
2691            // ("I need this in my editor"), so the offer names it instead of
2692            // announcing a daily limit they have not hit.
2693            if (typeof window.openPaywall === 'function') {
2694                window.openPaywall({
2695                    title: 'Start your free trial to download ' + format.toUpperCase(),
2696                    cta: 'export_' + format
2697                });
2698            } else {
2699                const m = document.getElementById('pay_prompt_modal');
2700                if (m) { try { m.showModal(); } catch (_) { m.setAttribute('open', ''); } }
2701                else { window.location.href = "/premium"; }
2702            }
2703            return false;
2704        }
2705
2706        event.preventDefault();
2707        _trackExportGate(format, 'signup_prompt');
2708        const next = window.location.pathname + '?export=' + encodeURIComponent(format);
2709        if (typeof window.openAuthModal === 'function') {
2710            window.openAuthModal({
2711                title: 'Create a free account to download',
2712                text: 'Your ' + format.toUpperCase() + ' will start downloading as soon as you’re signed in. It’s free, and this transcript will still be here.',
2713                next: next
2714            });
2715        } else {
2716            window.location.href = "/register?next=" + encodeURIComponent(next);
2717        }
2718        return false;
2719    };
2720
2721    // Coming back from signup with ?export=<format>: finish the download they
2722    // asked for, so creating the account is what delivered the file rather than
2723    // a detour they then have to repeat. The param is removed first so a refresh
2724    // or a back navigation cannot fire the download twice.
2725    (function () {
2726        const params = new URLSearchParams(window.location.search);
2727        const format = (params.get('export') || '').toLowerCase();
2728        // `from` only exists to label where a signup came from; it has done its
2729        // job by the time they are back here, so keep it out of shared URLs.
2730        if (!format && !params.has('from')) return;
2731        params.delete('export');
2732        params.delete('from');
2733        const clean = window.location.pathname + (params.toString() ? '?' + params.toString() : '') + window.location.hash;
2734        try { history.replaceState(null, '', clean); } catch (_) {}
2735
2736        if (!format || !IS_AUTHENTICATED) return;
2737        // A new account arrives with its free subtitle download unused.
2738        if (EXPORT_PRO_ONLY.indexOf(format) !== -1 && !IS_PREMIUM && !FREE_SUBTITLE_LEFT) return;
2739        const link = document.querySelector('a[href^="/api/export/' + format + '/"]');
2740        if (!link) return;
2741        setTimeout(function () { window.location.href = link.getAttribute('href'); }, 600);
2742
2743        // The download is a navigation to a file, so this page stays put. Offer
2744        // the trial once the file is on its way — after delivering what they
2745        // created the account for, not before, and never to someone already
2746        // paying. SRT/VTT are the natural next want for anyone exporting.
2747        if (IS_PREMIUM || window.__postExportOfferShown) return;
2748        window.__postExportOfferShown = true;
2749        setTimeout(function () {
2750            try {
2751                fetch('/api/track', {
2752                    method: 'POST', credentials: 'same-origin',
2753                    headers: {'Content-Type': 'application/json'},
2754                    body: JSON.stringify({event: 'post_export_offer_shown', meta: {format: format}})
2755                });
2756            } catch (_) {}
2757            if (typeof window.openPaywall === 'function') {
2758                window.openPaywall({
2759                    title: 'Your ' + format.toUpperCase() + ' is downloading',
2760                    cta: 'post_export'
2761                });
2762            }
2763        }, 4000);
2764    })();
2765</script>
2765
2766
2767
2768
2769<!-- For rendering markdown from AI -->
2770<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
2770
2771
2772
2773
2774
2775                
2776
2777
2778            </main>
2779
2780            <footer class="footer p-10 bg-neutral text-neutral-content">
2781                <aside>
2782                    <svg xmlns="http://www.w3.org/2000/svg" class="hidden w-12 h-12 fill-blue-500 sm:inline"
2783                        viewBox="0 0 256 256">
2784                        <path d="M232,64V192a8,8,0,0,1-8,8H32a8,8,0,0,1-8-8V64a8,8,0,0,1,8-8H224A8,8,0,0,1,232,64Z"
2785                            opacity="0.2"></path>
2786                        <path
2787                            d="M224,48H32A16,16,0,0,0,16,64V192a16,16,0,0,0,16,16H224a16,16,0,0,0,16-16V64A16,16,0,0,0,224,48Zm0,144H32V64H224V192ZM48,136a8,8,0,0,1,8-8H72a8,8,0,0,1,0,16H56A8,8,0,0,1,48,136Zm160,0a8,8,0,0,1-8,8H104a8,8,0,0,1,0-16h96A8,8,0,0,1,208,136Zm-48,32a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16h96A8,8,0,0,1,160,168Zm48,0a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16h16A8,8,0,0,1,208,168Z">
2788                        </path>
2789                    </svg>
2790                    <p>Transcript.you<br>&copy; 2026 All rights reserved.</p>
2791
2792                    <!-- Company & Legal Section (moved under copyright) -->
2793                    <nav class="flex flex-col gap-2 mt-6">
2794                        <h6 class="footer-title">Company</h6>
2795                        <a href="/refer" class="link link-hover">Refer & Earn</a>
2796                        <a href="/affiliate" class="link link-hover">Affiliate Program</a>
2797                        <a href="/blog/" class="link link-hover">Our Blog</a>
2798                        <a href="/about" class="link link-hover">About Us</a>
2799                        <a href="/contact" class="link link-hover">Contact</a>
2800                        <h6 class="footer-title mt-4">Resources</h6>
2801                        <a href="/transcription-guide" class="link link-hover">Transcription Guide</a>
2802                        <a href="/glossary" class="link link-hover">Glossary</a>
2803                        <a href="/vs" class="link link-hover">Tool Comparisons</a>
2804                        <h6 class="footer-title mt-4">Explore by Topic</h6>
2805                        <a href="/subtitles-and-captions" class="link link-hover">Subtitles &amp; Captions</a>
2806                        <a href="/audio-transcription" class="link link-hover">Audio Transcription</a>
2807                        <a href="/video-transcription" class="link link-hover">Video Transcription</a>
2808                        <a href="/output-formats" class="link link-hover">Output Formats</a>
2809                        <a href="/use-cases" class="link link-hover">Use Cases</a>
2810                        <h6 class="footer-title mt-4">For You</h6>
2811                        <a href="/for/podcasters" class="link link-hover">For Podcasters</a>
2812                        <a href="/for/students" class="link link-hover">For Students</a>
2813                        <a href="/for/journalists" class="link link-hover">
2813For Journalists</a>
2814                        <a href="/for/researchers" class="link link-hover">For Researchers</a>
2815                        <a href="/for/content-creators" class="link link-hover">For Content Creators</a>
2816                        <!-- Legal Section within same column -->
2817                        <h6 class="footer-title mt-4">Legal</h6>
2818                        <a href="/terms" class="link link-hover">Terms of Use</a>
2819                        <a href="/privacy" class="link link-hover">Privacy Policy</a>
2820                    </nav>
2821                </aside>
2822                <!-- START: MODIFIED SECTION -->
2823
2824                <!-- Audio Conversion Column -->
2825                <nav>
2826                    <h6 class="footer-title">Audio Conversion</h6>
2827                    <a class="link link-hover" href="/transcribe-audio">Transcribe Audio</a>
2828                    <a class="link link-hover" href="/mp3-to-text">MP3 to Text</a>
2829                    <a class="link link-hover" href="/wav-to-text">WAV to Text</a>
2830                    <a class="link link-hover" href="/m4a-to-text">M4A to Text</a>
2831                    <a class="link link-hover" href="/aac-to-text">AAC to Text</a>
2832                    <a class="link link-hover" href="/ogg-to-text">OGG to Text</a>
2833                    <a class="link link-hover" href="/flac-to-text">FLAC to Text</a>
2834                    <a class="link link-hover" href="/wma-to-text">WMA to Text</a>
2835                    <a class="link link-hover" href="/mp3-to-transcript">MP3 to
2836                        Transcript</a>
2837                    <a class="link link-hover" href="/speech-to-text">Speech to
2838                        Text</a>
2839                    <a class="link link-hover" href="/voice-to-text">Voice to
2840                        Text</a>
2841                    <a class="link link-hover" href="/audio-to-text">Audio to
2842                        Text</a>
2843                    <a class="link link-hover" href="/voice-memo-to-text">Voice Memo
2844                        to Text</a>
2845                    <a class="link link-hover" href="/dictation-to-text">Dictation
2846                        to Text</a>
2847                </nav>
2848
2849                <!-- Video Conversion Column -->
2850                <nav>
2851                    <h6 class="footer-title">Video Conversion</h6>
2852                    <a class="link link-hover" href="/tube">Youtube Transcript</a>
2853                    <a class="link link-hover" href="/mp4-to-text">MP4 to Text</a>
2854                    <a class="link link-hover" href="/mov-to-text">MOV to Text</a>
2855                    <a class="link link-hover" href="/mkv-to-text">MKV to Text</a>
2856                    <a class="link link-hover" href="/avi-to-text">AVI to Text</a>
2857                    <a class="link link-hover" href="/webm-to-text">WebM to Text</a>
2858                    <a class="link link-hover" href="/wmv-to-text">WMV to Text</a>
2859                    <a class="link link-hover" href="/transcribe-youtube-videos">Transcribe
2860                        YouTube</a>
2861                    <a class="link link-hover" href="/video-to-text">Video to
2862                        Text</a>
2863                    <a class="link link-hover" href="/transcribe-video">Transcribe
2864                        Video</a>
2865                    <a class="link link-hover" href="/video-to-transcript">Video to
2866                        Transcript</a>
2867                    <a class="link link-hover" href="/convert-video-to-text">Convert
2868                        Video to Text</a>
2869                </nav>
2870
2871                <!-- Use Cases Column -->
2872                <nav>
2873                    <h6 class="footer-title">Use Cases</h6>
2874                    <a class="link link-hover" href="/zoom-to-text">Zoom to Text</a>
2875                    <a class="link link-hover" href="/google-meet-to-text">Google
2876                        Meet to Text</a>
2877                    <a class="link link-hover" href="/teams-to-text">Teams to
2878                        Text</a>
2879                    <a class="link link-hover" href="/podcast-to-text">Podcast to
2880                        Text</a>
2881                    <a class="link link-hover" href="/interview-transcription">Interview
2882                        Transcription</a>
2883                    <a class="link link-hover" href="/meeting-transcription">Meeting
2884                        Transcription</a>
2885                    <a class="link link-hover" href="/lecture-to-text">Lecture to
2886                        Text</a>
2887                    <a class="link link-hover" href="/sermon-to-text">Sermon to
2888                        Text</a>
2889                    <a class="link link-hover" href="/legal-transcription">Legal
2890                        Transcription</a>
2891                    <a class="link link-hover" href="/medical-transcription">Medical
2892                        Transcription</a>
2893                    <a class="link link-hover" href="/convert-audio-to-microsoft-word">Audio to
2894                        Word</a>
2895                </nav>
2896
2897                <!-- Tools & Downloaders Column -->
2898                <nav>
2899                    <h6 class="footer-title">Media Downloaders</h6>
2900                    <a class="link link-hover" href="/youtube-downloader">YouTube Downloader</a>
2901                    <a class="link link-hover" href="/facebook-downloader">Facebook Downloader</a>
2902                    <a class="link link-hover" href="/tiktok-downloader">TikTok Downloader</a>
2903                    <a class="link link-hover" href="/tiktok">TikTok Transcript</a>
2904                    <a class="link link-hover" href="/bulk-tiktok">Bulk TikTok Transcripts</a>
2905                    <a class="link link-hover" href="/instagram-downloader">Instagram Downloader</a>
2906                    <a class="link link-hover" href="/x-downloader">Twitter (X) Downloader</a>
2907                </nav>
2908
2909                <!-- Subtitles & Captions Column -->
2910                <nav>
2911                    <h6 class="footer-title">Subtitles &amp; Captions</h6>
2912                    <a class="link link-hover font-semibold" href="/subtitles-and-captions">All Subtitle Tools</a>
2913                    <a class="link link-hover" href="/subtitle-downloader">Download YouTube Subtitles</a>
2914                    <a class="link link-hover" href="/generate-subtitles">Generate Subtitles</a>
2915                    <a class="link link-hover" href="/audio-to-srt">Audio to SRT</a>
2916                    <a class="link link-hover" href="/video-to-srt">Video to SRT</a>
2917                    <a class="link link-hover" href="/audio-to-vtt">Audio to VTT</a>
2918                    <a class="link link-hover" href="/video-to-vtt">Video to VTT</a>
2919                    <a class="link link-hover" href="/add-captions-to-video">Add Captions to Video</a>
2920                    <a class="link link-hover" href="/free-subtitles">Free Subtitles</a>
2921</nav>
2922                
2923                <nav>
2924                    <h6 class="footer-title">Featured Articles</h6>
2925                    
2926                    <a href="/blog/best-voice-to-text-tools" class="link link-hover">Best Voice-to-Text Tools 2026</a>
2927                    
2928                    <a href="/blog/best-tiktok-transcript-tool" class="link link-hover">Best TikTok Transcript Tool 2026</a>
2929                    
2930                    <a href="/blog/best-speech-to-text-software-2026" class="link link-hover">
2930Best Speech-to-Text Software 2026</a>
2931                    
2932                    <a href="/blog/best-audio-to-text-converter" class="link link-hover">Best Audio-to-Text Converter 2026</a>
2933                    
2934                    <a href="/blog/best-transcription-software-online" class="link link-hover">Best Transcription Software Online</a>
2935                    
2936                </nav>
2937                
2938            </footer>
2939            <div class="footer footer-center p-4 bg-neutral text-neutral-content border-t border-white/5">
2940                <aside>
2941                    <p class="text-xs opacity-40">Transcript.You is not affiliated with YouTube, TikTok, or any other
2942                        third-party platform mentioned on this site. Our services are for educational and personal use
2943                        only.</p>
2944                </aside>
2945            </div>
2946        </div>
2947        <div class="drawer-side z-[60]">
2948            <label for="my-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
2949            <div class="flex flex-col h-full w-4/5 bg-base-100 text-base-content overflow-y-auto">
2950                <div class="p-4 pb-40">
2951                    <!-- Sidebar Header -->
2952                    <div class="flex justify-between items-center mb-4">
2953                        <a href="/" class="btn btn-ghost normal-case text-xl">
2954                            <svg xmlns="http://www.w3.org/2000/svg" class="w-8 h-8 fill-blue-500" viewBox="0 0 256 256">
2955                                <path
2956                                    d="M232,64V192a8,8,0,0,1-8,8H32a8,8,0,0,1-8-8V64a8,8,0,0,1,8-8H224A8,8,0,0,1,232,64Z"
2957                                    opacity="0.2"></path>
2958                                <path
2959                                    d="M224,48H32A16,16,0,0,0,16,64V192a16,16,0,0,0,16,16H224a16,16,0,0,0,16-16V64A16,16,0,0,0,224,48Zm0,144H32V64H224V192ZM48,136a8,8,0,0,1,8-8H72a8,8,0,0,1,0,16H56A8,8,0,0,1,48,136Zm160,0a8,8,0,0,1-8,8H104a8,8,0,0,1,0-16h96A8,8,0,0,1,208,136Zm-48,32a8,8,0,0,1-8,8H56a8,8,0,0,1,0-16h96A8,8,0,0,1,160,168Zm48,0a8,8,0,0,1-8,8H184a8,8,0,0,1,0-16h16A8,8,0,0,1,208,168Z">
2960                                </path>
2961                            </svg>
2962                            Transcript.you
2963                        </a>
2964                        <label for="my-drawer" class="btn btn-ghost btn-circle">✕</label>
2965                    </div>
2966                    <!-- Sidebar Menu -->
2967                    <!-- START: MODIFIED SECTION -->
2968                    <ul class="menu p-0">
2969                        <!-- PRIMARY FUNCTIONS FIRST -->
2970                        <li><a href="/tube" class="py-3"><i
2971                                    class="ph-duotone ph-youtube-logo text-red-600"></i> YouTube Transcript</a></li>
2972                        <li><a href="/shorts" class="py-3"><i
2973                                    class="ph-duotone ph-video-camera text-orange-600"></i> Shorts</a></li>
2974                        <li><a href="/tiktok" class="py-3"><i
2975                                    class="ph-duotone ph-tiktok-logo text-slate-800"></i> TikTok</a></li>
2976                        <li><a href="/transcribe-audio" class="py-3"><i
2977                                    class="ph-duotone ph-microphone-stage text-purple-600"></i> Audio</a>
2978                        </li>
2979                        <li class="group">
2980                            <details class="[&_summary::-webkit-details-marker]:hidden">
2981                                <summary
2982                                    class="flex items-center justify-between py-3 cursor-pointer hover:bg-base-200 transition-colors list-none text-gray-700 rounded-lg">
2983                                    <div class="flex items-center">
2984                                        <i class="ph-duotone ph-subtitles text-violet-600 mr-3"></i>
2985                                        <a href="/subtitles-and-captions"
2986                                           onclick="event.stopPropagation();"
2987                                           class="hover:underline">Subtitles</a>
2988                                    </div>
2989                                    <i class="ph-bold ph-caret-down text-[10px] transition-transform group-open:rotate-180 mr-4 opacity-50"></i>
2990                                </summary>
2991                                <ul class="menu menu-sm p-0 pl-4 border-l border-base-200 ml-5 mt-1">
2992                                    <li><a href="/subtitle-downloader" class="py-2">Download YouTube Subtitles</a></li>
2993                                    <li><a href="/generate-subtitles" class="py-2">Generate Subtitles</a></li>
2994                                    <li><a href="/video-to-srt" class="py-2">Video to SRT</a></li>
2995                                    <li><a href="/audio-to-srt" class="py-2">Audio to SRT</a></li>
2996                                    <li><a href="/add-captions-to-video" class="py-2">Add Captions to Video</a></li>
2997                                    <li><a href="/subtitles-and-captions" class="py-2 font-semibold">All Subtitle &amp; Caption Tools</a></li>
2998                                </ul>
2999                            </details>
3000                        </li>
3001                        <li class="group">
3002                            <details class="[&_summary::-webkit-details-marker]:hidden">
3003                                <summary
3004                                    class="flex items-center justify-between py-3 cursor-pointer hover:bg-base-200 transition-colors list-none text-gray-700 rounded-lg">
3005                                    <div class="flex items-center">
3006                                        <i class="ph-duotone ph-stack text-indigo-600 mr-3"></i>
3007                                        
3008                                        <a href="/bulk"
3009                                           onclick="event.stopPropagation();"
3010                                           class="hover:underline">Bulk Transcriptions</a>
3011                                    </div>
3012                                    <i class="ph-bold ph-caret-down text-[10px] transition-transform group-open:rotate-180 mr-4 opacity-50"></i>
3013                                </summary>
3014                                <ul class="menu menu-sm p-0 pl-4 border-l border-base-200 ml-5 mt-1">
3015                                    <li><a href="/bulk-transcripts" class="py-2"><i
3016                                                class="ph-duotone ph-youtube-logo text-red-500"></i> Bulk YouTube Transcripts</a></li>
3017                                    <li><a href="/bulk-tiktok" class="py-2"><i
3018                                                class="ph-duotone ph-tiktok-logo text-pink-500"></i> Bulk TikTok Transcripts</a></li>
3019                                    <li><a href="/bulk-spotify" class="py-2"><i
3020                                                class="ph-duotone ph-spotify-logo text-green-500"></i> Bulk Spotify Transcripts</a></li>
3021                                    <li><a href="/bulk-vimeo" class="py-2"><i
3022                                                class="ph-duotone ph-video text-sky-500"></i> Bulk Vimeo Transcripts</a></li>
3023                                    <li><a href="/bulk-twitch" class="py-2"><i
3024                                                class="ph-duotone ph-twitch-logo text-purple-600"></i> Bulk Twitch Transcripts</a></li>
3025                                    <li><a href="/bulk-audio" class="py-2"><i
3026                                                class="ph-duotone ph-microphone text-emerald-500"></i> Bulk Audio Transcription</a></li>
3027                                </ul>
3028                            </details>
3029                        </li>
3030                        
3031                        <li><a href="/files" class="py-3"><i
3032                                    class="ph-duotone ph-folder-open text-blue-600"></i> Files <span
3033                                    class="task-count-badge ml-1"></span></a></li>
3034                        <li class="group">
3035                            <details class="[&_summary::-webkit-details-marker]:hidden">
3036                                <summary
3037                                    class="flex items-center justify-between py-3 cursor-pointer hover:bg-base-200 transition-colors list-none text-gray-700 rounded-lg">
3038                                    <div class="flex items-center">
3039                                        <i class="ph-duotone ph-download-simple text-indigo-600 mr-3"></i>
3040                                        <span>Media Downloaders</span>
3041                                    </div>
3042                                    <i
3043                                        class="ph-bold ph-caret-down text-[10px] transition-transform group-open:rotate-180 mr-4 opacity-50"></i>
3044                                </summary>
3045                                <ul class="menu menu-sm p-0 pl-4 border-l border-base-200 ml-5 mt-1">
3046                                    <li><a href="/youtube-downloader" class="py-2"><i
3047                                                class="ph-duotone ph-youtube-logo text-red-600"></i> YouTube
3048                                            Downloader</a>
3049                                    </li>
3050                                    <li><a href="/facebook-downloader" class="py-2"><i
3051                                                class="ph-duotone ph-facebook-logo text-blue-600"></i> Facebook
3052                                            Downloader</a></li>
3053                                    <li><a href="/tiktok-downloader" class="py-2"><i
3054                                                class="ph-duotone ph-tiktok-logo text-slate-800"></i> TikTok
3055                                            Downloader</a>
3056                                    </li>
3057                                    <li><a href="/instagram-downloader" class="py-2"><i
3058                                                class="ph-duotone ph-instagram-logo text-pink-600"></i> Instagram
3059                                            Downloader</a></li>
3060                                    <li><a href="/x-downloader" class="py-2"><i
3061                                                class="ph-duotone ph-x-logo text-slate-900"></i> Twitter (X)
3062                                            Downloader</a>
3063                                    </li>
3064                                    <li><a href="/subtitle-downloader" class="py-2"><i
3065                                                class="ph-duotone ph-subtitles text-violet-600"></i> Subtitle
3066                                            Downloader</a>
3067                                    </li>
3068                                </ul>
3069                            </details>
3070                        </li>
3071
3072                        <li><a href="/refer" class="py-3 text-indigo-600 font-bold"><i
3073                                    class="ph-duotone ph-gift text-indigo-600"></i>
3074                                Refer</a></li>
3075
3076                        <div class="divider my-2 opacity-50">Resources</div>
3077                        <li><a href="/transcription-guide" class="py-3"><i
3078                                    class="ph-duotone ph-book-open text-blue-600"></i> Transcription Guide</a></li>
3079                        <li><a href="/glossary" class="py-3"><i
3080                                    class="ph-duotone ph-list-magnifying-glass text-emerald-600"></i> Glossary</a></li>
3081                        <li><a href="/vs" class="py-3"><i
3082                                    class="ph-duotone ph-scales text-amber-600"></i> Tool Comparisons</a></li>
3083
3084                        <div class="divider my-2 opacity-50">For You</div>
3085                        <li><a href="/for/podcasters" class="py-3"><i class="ph-duotone ph-microphone text-purple-600"></i> Podcasters</a></li>
3086                        <li><a href="/for/students" class="py-3"><i class="ph-duotone ph-graduation-cap text-blue-600"></i> Students</a></li>
3087                        <li><a href="/for/journalists" class="py-3"><i class="ph-duotone ph-newspaper text-slate-700"></i> Journalists</a></li>
3088                        <li><a href="/for/researchers" class="py-3"><i class="ph-duotone ph-flask text-emerald-600"></i> Researchers</a></li>
3089                        <li><a href="/for/content-creators" class="py-3"><i class="ph-duotone ph-video text-rose-600"></i> Content Creators</a></li>
3090
3091                        
3092                    </ul>
3093                    <!-- END: MODIFIED SECTION -->
3094                </div>
3095                <!-- Auth buttons at the bottom -->
3096                <div class="mt-auto p-4 border-t border-base-200 bg-base-100 sticky bottom-0 z-10">
3097                    
3098                    <div class="space-y-2">
3099                        <a href="/premium"
3100                            class="btn btn-ghost w-full border border-gray-300">View Pricing</a>
3101                        <a href="/register" class="btn btn-warning w-full">
3102                            <i class="ph-duotone ph-user-plus text-lg mr-2"></i>
3103                            Register
3104                        </a>
3105                        <a href="/login" class="btn btn-primary w-full">
3106                            <i class="ph-duotone ph-sign-in text-lg mr-2"></i>
3107                            Login
3108                        </a>
3109                    </div>
3110                    
3111                </div>
3112            </div>
3113        </div>
3114    </div>
3115
3116    
3116<script>
3117        // Bookmark shortcut OS detection
3118        document.addEventListener('DOMContentLoaded', function () {
3119            if (navigator.userAgent.indexOf('Mac OS X') != -1) {
3120                document.querySelectorAll('.macos').forEach(el => el.classList.remove('hidden'));
3121            } else {
3122                document.querySelectorAll('.window').forEach(el => el.classList.remove('hidden'));
3123            }
3124        });
3125    </script>
3125
3126
3127    
3127<script>
3128        document.addEventListener('DOMContentLoaded', function () {
3129            const isAuthenticated = document.body.dataset.authenticated === 'true';
3130
3131            // Only run this for guest users
3132            if (!isAuthenticated) {
3133                try {
3134                    const guestTasks = JSON.parse(localStorage.getItem('guestTranscriptionTasks')) || [];
3135                    const count = guestTasks.length;
3136
3137                    if (count > 0) {
3138                        // Find all badge elements and update them
3139                        document.querySelectorAll('.task-count-badge').forEach(el => {
3140                            el.textContent = `(${count})`;
3141                        });
3142                    }
3143                } catch (e) {
3144                    console.error("Could not parse guest tasks from localStorage:", e);
3145                }
3146            }
3147        });
3148    </script>
3148
3149
3150    
3150<script>
3151        // Function to set a cookie
3152        function setCookie(name, value, days) {
3153            var expires = "";
3154            if (days) {
3155                var date = new Date();
3156                date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
3157                expires = "; expires=" + date.toUTCString();
3158            }
3159            document.cookie = name + "=" + (value || "") + expires + "; path=/";
3160        }
3161
3162        // Function to get URL parameter
3163        function getParam(p) {
3164            var match = RegExp('[?&]' + p + '=([^&]*)').exec(window.location.search);
3165            return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
3166        }
3167
3168        // 1. Check if gclid is in the URL
3169        var gclid = getParam('gclid');
3170
3171        // 2. If found, save it to a cookie for 90 days
3172        if (gclid) {
3173            setCookie('gclid', gclid, 90);
3174        }
3175    </script>
3175
3176
3177<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"c4d9333929ba4b06b0a7f41cb7c20dd2","r":1,"spa":2}' crossorigin="anonymous"></script>
3177
3178</body>
3179
3180</html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.