| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Repo2TXT | GitHub to Text Converter</title> |
| <meta name="description" content="Convert any GitHub repository into a single high-quality text file for easy analysis and documentation."> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&family=Outfit:wght@400;700&display=swap" rel="stylesheet"> |
| <style> |
| :root { |
| --primary: #6366f1; |
| --primary-glow: rgba(99, 102, 241, 0.5); |
| --accent: #a855f7; |
| --bg: #0f172a; |
| --card-bg: rgba(30, 41, 59, 0.7); |
| --text-main: #f8fafc; |
| --text-muted: #94a3b8; |
| --border: rgba(255, 255, 255, 0.1); |
| } |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| } |
| |
| body { |
| font-family: 'Inter', sans-serif; |
| background-color: var(--bg); |
| color: var(--text-main); |
| min-height: 100vh; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| overflow-x: hidden; |
| position: relative; |
| } |
| |
| |
| body::before { |
| content: ''; |
| position: absolute; |
| top: -10%; |
| left: -10%; |
| width: 40%; |
| height: 40%; |
| background: radial-gradient(circle, var(--primary-glow) 0%, transparent 70%); |
| z-index: -1; |
| filter: blur(80px); |
| animation: move 20s infinite alternate; |
| } |
| |
| body::after { |
| content: ''; |
| position: absolute; |
| bottom: -10%; |
| right: -10%; |
| width: 40%; |
| height: 40%; |
| background: radial-gradient(circle, rgba(168, 85, 247, 0.3) 0%, transparent 70%); |
| z-index: -1; |
| filter: blur(80px); |
| animation: move 25s infinite alternate-reverse; |
| } |
| |
| @keyframes move { |
| from { transform: translate(0, 0); } |
| to { transform: translate(100px, 100px); } |
| } |
| |
| .container { |
| width: 90%; |
| max-width: 600px; |
| padding: 2.5rem; |
| background: var(--card-bg); |
| backdrop-filter: blur(12px); |
| -webkit-backdrop-filter: blur(12px); |
| border: 1px solid var(--border); |
| border-radius: 24px; |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); |
| text-align: center; |
| z-index: 10; |
| animation: fadeIn 0.8s ease-out; |
| } |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(20px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| h1 { |
| font-family: 'Outfit', sans-serif; |
| font-size: 2.5rem; |
| font-weight: 800; |
| margin-bottom: 0.5rem; |
| background: linear-gradient(135deg, #fff 0%, var(--primary) 100%); |
| -webkit-background-clip: text; |
| -webkit-text-fill-color: transparent; |
| } |
| |
| p.subtitle { |
| color: var(--text-muted); |
| margin-bottom: 2rem; |
| font-size: 1.1rem; |
| } |
| |
| .input-group { |
| position: relative; |
| margin-bottom: 1.5rem; |
| } |
| |
| input { |
| width: 100%; |
| padding: 1.2rem 1.5rem; |
| background: rgba(15, 23, 42, 0.6); |
| border: 1px solid var(--border); |
| border-radius: 16px; |
| color: white; |
| font-size: 1rem; |
| transition: all 0.3s ease; |
| outline: none; |
| } |
| |
| input:focus { |
| border-color: var(--primary); |
| box-shadow: 0 0 0 4px rgba(99, 102, 241, 0.2); |
| background: rgba(15, 23, 42, 0.8); |
| } |
| |
| button { |
| width: 100%; |
| padding: 1.2rem; |
| background: linear-gradient(135deg, var(--primary) 0%, var(--accent) 100%); |
| border: none; |
| border-radius: 16px; |
| color: white; |
| font-family: 'Outfit', sans-serif; |
| font-size: 1.1rem; |
| font-weight: 700; |
| cursor: pointer; |
| transition: all 0.3s ease; |
| box-shadow: 0 10px 15px -3px rgba(99, 102, 241, 0.4); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| gap: 10px; |
| } |
| |
| button:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 20px 25px -5px rgba(99, 102, 241, 0.5); |
| filter: brightness(1.1); |
| } |
| |
| button:active { |
| transform: translateY(0); |
| } |
| |
| button:disabled { |
| background: #475569; |
| cursor: not-allowed; |
| transform: none; |
| box-shadow: none; |
| } |
| |
| .loader { |
| display: none; |
| width: 20px; |
| height: 20px; |
| border: 3px solid rgba(255, 255, 255, 0.3); |
| border-radius: 50%; |
| border-top-color: #fff; |
| animation: spin 0.8s linear infinite; |
| } |
| |
| @keyframes spin { |
| to { transform: rotate(360deg); } |
| } |
| |
| .status { |
| margin-top: 1.5rem; |
| font-size: 0.9rem; |
| min-height: 1.2rem; |
| transition: color 0.3s ease; |
| } |
| |
| .success { color: #10b981; } |
| .error { color: #ef4444; } |
| |
| |
| .process-log { |
| margin-top: 2rem; |
| text-align: left; |
| background: rgba(15, 23, 42, 0.4); |
| padding: 1.5rem; |
| border-radius: 16px; |
| border: 1px solid var(--border); |
| } |
| |
| .step { |
| display: flex; |
| align-items: center; |
| gap: 12px; |
| margin-bottom: 12px; |
| color: var(--text-muted); |
| font-size: 0.9rem; |
| transition: all 0.3s ease; |
| } |
| |
| .step.active { |
| color: var(--text-main); |
| } |
| |
| .step.completed { |
| color: #10b981; |
| } |
| |
| .step-dot { |
| width: 8px; |
| height: 8px; |
| background: #475569; |
| border-radius: 50%; |
| transition: all 0.3s ease; |
| } |
| |
| .step.active .step-dot { |
| background: var(--primary); |
| box-shadow: 0 0 10px var(--primary); |
| transform: scale(1.2); |
| } |
| |
| .step.completed .step-dot { |
| background: #10b981; |
| } |
| |
| footer { |
| margin-top: 2rem; |
| color: var(--text-muted); |
| font-size: 0.8rem; |
| } |
| |
| |
| .glow-effect { |
| position: absolute; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| pointer-events: none; |
| z-index: -1; |
| border-radius: 16px; |
| transition: opacity 0.3s; |
| opacity: 0; |
| box-shadow: 0 0 20px var(--primary); |
| } |
| |
| input:focus + .glow-effect { |
| opacity: 0.3; |
| } |
| </style> |
| </head> |
| <body> |
|
|
| <div class="container"> |
| <h1>Repo2TXT</h1> |
| <p class="subtitle">Convert any GitHub repo to a single .txt file</p> |
|
|
| <div class="input-group"> |
| <input id="repoUrl" type="text" placeholder="https://github.com/username/repo" autocomplete="off"> |
| <div class="glow-effect"></div> |
| </div> |
|
|
| <button id="downloadBtn" onclick="handleDownload()"> |
| <span id="btnText">Generate project.txt</span> |
| <div id="loader" class="loader"></div> |
| </button> |
|
|
| <div id="status" class="status"></div> |
|
|
| <div id="processLog" class="process-log" style="display: none;"> |
| <div class="step" id="step1"> |
| <div class="step-dot"></div> |
| <span>Validating repository URL...</span> |
| </div> |
| <div class="step" id="step2"> |
| <div class="step-dot"></div> |
| <span>Establishing connection to backend...</span> |
| </div> |
| <div class="step" id="step3"> |
| <div class="step-dot"></div> |
| <span>Cloning repository (sparse checkout)...</span> |
| </div> |
| <div class="step" id="step4"> |
| <div class="step-dot"></div> |
| <span>Filtering and merging file contents...</span> |
| </div> |
| <div class="step" id="step5"> |
| <div class="step-dot"></div> |
| <span>Finalizing project.txt...</span> |
| </div> |
| </div> |
| </div> |
|
|
| <footer> |
| © 2024 Repo2TXT Premium. Processing is fast and secure. |
| </footer> |
|
|
| <script> |
| async function handleDownload() { |
| const urlInput = document.getElementById("repoUrl"); |
| const btn = document.getElementById("downloadBtn"); |
| const btnText = document.getElementById("btnText"); |
| const loader = document.getElementById("loader"); |
| const status = document.getElementById("status"); |
| const processLog = document.getElementById("processLog"); |
| |
| const repoUrl = urlInput.value.trim(); |
| |
| if (!repoUrl) { |
| showStatus("Please enter a valid GitHub URL", "error"); |
| return; |
| } |
| |
| if (!repoUrl.includes("github.com")) { |
| showStatus("Invalid URL. Must be a GitHub repository.", "error"); |
| return; |
| } |
| |
| |
| btn.disabled = true; |
| btnText.textContent = "Processing..."; |
| loader.style.display = "block"; |
| status.textContent = ""; |
| status.className = "status"; |
| processLog.style.display = "block"; |
| |
| |
| document.querySelectorAll('.step').forEach(s => s.className = 'step'); |
| |
| const setStep = (id, state) => { |
| const el = document.getElementById(id); |
| if (el) el.className = `step ${state}`; |
| }; |
| |
| try { |
| |
| setStep('step1', 'active'); |
| await new Promise(r => setTimeout(r, 600)); |
| setStep('step1', 'completed'); |
| |
| |
| setStep('step2', 'active'); |
| await new Promise(r => setTimeout(r, 400)); |
| |
| |
| const fetchPromise = fetch("/download", { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json" |
| }, |
| body: JSON.stringify({ url: repoUrl }) |
| }); |
| |
| |
| setStep('step2', 'completed'); |
| setStep('step3', 'active'); |
| |
| const response = await fetchPromise; |
| |
| if (!response.ok) { |
| const errorData = await response.json(); |
| throw new Error(errorData.detail || "Processing failed"); |
| } |
| |
| setStep('step3', 'completed'); |
| setStep('step4', 'active'); |
| await new Promise(r => setTimeout(r, 800)); |
| setStep('step4', 'completed'); |
| |
| setStep('step5', 'active'); |
| const blob = await response.blob(); |
| setStep('step5', 'completed'); |
| |
| |
| const contentDisposition = response.headers.get('Content-Disposition'); |
| let filename = "project.txt"; |
| if (contentDisposition && contentDisposition.indexOf('attachment') !== -1) { |
| const filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; |
| const matches = filenameRegex.exec(contentDisposition); |
| if (matches != null && matches[1]) { |
| filename = matches[1].replace(/[ '"]/g, ''); |
| } |
| } |
| |
| const downloadUrl = window.URL.createObjectURL(blob); |
| const link = document.createElement("a"); |
| link.href = downloadUrl; |
| link.download = filename; |
| document.body.appendChild(link); |
| link.click(); |
| link.remove(); |
| window.URL.revokeObjectURL(downloadUrl); |
| |
| showStatus("Success! Your file is ready.", "success"); |
| } catch (err) { |
| console.error(err); |
| showStatus(err.message, "error"); |
| |
| document.querySelectorAll('.step.active').forEach(s => s.classList.add('error-step')); |
| } finally { |
| btn.disabled = false; |
| btnText.textContent = "Generate project.txt"; |
| loader.style.display = "none"; |
| } |
| } |
| |
| function showStatus(message, type) { |
| const status = document.getElementById("status"); |
| status.textContent = message; |
| status.className = `status ${type}`; |
| } |
| |
| |
| document.getElementById("repoUrl").addEventListener("keypress", function(e) { |
| if (e.key === "Enter") { |
| handleDownload(); |
| } |
| }); |
| </script> |
|
|
| </body> |
| </html> |
|
|