mirror of
https://github.com/multipleof4/4ev.link.git
synced 2026-01-13 15:57:53 +00:00
114 lines
5.9 KiB
HTML
114 lines
5.9 KiB
HTML
<!doctype html>
|
|
<html lang="en" class="dark">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>4ev.link — Fast, Free & Permanent URL Shortener</title>
|
|
<meta name="description" content="Create short, memorable, and permanent links for free. 4ev.link is a simple, fast, and reliable URL shortener for all your needs.">
|
|
<meta name="color-scheme" content="light dark">
|
|
<meta name="theme-color" content="#f9fafb" media="(prefers-color-scheme: light)">
|
|
<meta name="theme-color" content="#0a0a0a" media="(prefers-color-scheme: dark)">
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/lucide@latest"></script>
|
|
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
|
</head>
|
|
<body class="bg-gray-50 dark:bg-neutral-950 text-gray-800 dark:text-gray-300 antialiased">
|
|
<div class="flex flex-col min-h-screen">
|
|
<header class="py-4">
|
|
<nav class="container mx-auto px-6 flex justify-between items-center">
|
|
<a href="/" class="font-light text-xl text-black dark:text-white">4ev.link</a>
|
|
<button class="text-sm font-semibold py-2 px-4 rounded-lg bg-gray-200 dark:bg-neutral-800 text-black dark:text-white hover:opacity-80 transition-opacity">
|
|
Sign In
|
|
</button>
|
|
</nav>
|
|
</header>
|
|
|
|
<main class="flex-grow container mx-auto px-6 text-center flex flex-col justify-center">
|
|
<section class="py-16 sm:py-20">
|
|
<h1 class="text-4xl sm:text-6xl font-extrabold tracking-tight text-black dark:text-white">
|
|
Links that last <span class="text-gray-400 dark:text-neutral-500">forever</span>.
|
|
</h1>
|
|
<p class="mt-4 max-w-2xl mx-auto text-lg text-gray-600 dark:text-neutral-400">
|
|
A simple, fast, and reliable URL shortener for everyone.
|
|
</p>
|
|
|
|
<form id="shorten-form" class="mt-8 max-w-xl mx-auto flex flex-col gap-4">
|
|
<div class="flex flex-col sm:flex-row gap-3">
|
|
<label class="relative flex-1">
|
|
<i data-lucide="link" class="absolute top-1/2 left-4 -translate-y-1/2 w-5 h-5 text-gray-400 dark:text-neutral-500"></i>
|
|
<input id="url-input" type="text" placeholder="your-long-link.com" autocapitalize="off" spellcheck="false" class="w-full pl-11 pr-4 py-3 bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800 rounded-lg focus:ring-2 focus:ring-black dark:focus:ring-white focus:outline-none transition">
|
|
</label>
|
|
<button id="submit-button" type="submit" class="px-6 py-3 font-semibold bg-black dark:bg-white text-white dark:text-black rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-wait">
|
|
Shorten
|
|
</button>
|
|
</div>
|
|
<div class="g-recaptcha self-center" data-sitekey="6LeXhdYrAAAAALW6DdgxNeHU0kwBncdicLnVYvXT"></div>
|
|
</form>
|
|
|
|
<div id="result-container" hidden class="mt-4 max-w-xl mx-auto flex gap-2">
|
|
<input id="result-output" type="text" readonly class="flex-1 p-3 bg-gray-100 dark:bg-neutral-900 border border-gray-200 dark:border-neutral-800 rounded-lg text-sm text-center truncate">
|
|
<button id="copy-button" type="button" title="Copy to clipboard" class="p-3 bg-gray-200 dark:bg-neutral-800 rounded-lg hover:opacity-80 transition-opacity">
|
|
<i data-lucide="copy" class="w-5 h-5"></i>
|
|
</button>
|
|
<a id="open-link-button" target="_blank" rel="noopener" title="Open link in new tab" class="p-3 bg-gray-200 dark:bg-neutral-800 rounded-lg hover:opacity-80 transition-opacity">
|
|
<i data-lucide="arrow-up-right" class="w-5 h-5"></i>
|
|
</a>
|
|
</div>
|
|
|
|
<p id="message-area" class="mt-3 text-red-500 text-sm h-5"></p>
|
|
</section>
|
|
</main>
|
|
|
|
<footer class="text-center py-8">
|
|
<p class="text-sm text-gray-500 dark:text-neutral-500">© 2024 4ev.link. All Rights Reserved.</p>
|
|
</footer>
|
|
</div>
|
|
|
|
<script>
|
|
const $=q=>document.querySelector(q),form=$('#shorten-form'),urlInput=$('#url-input'),submitButton=$('#submit-button'),messageArea=$('#message-area'),resultContainer=$('#result-container'),resultOutput=$('#result-output'),copyButton=$('#copy-button'),openLinkButton=$('#open-link-button'),normalizeUrl=s=>{try{return new URL(s).href}catch(_){try{return new URL('https://'+s).href}catch(_){}}};
|
|
|
|
form.onsubmit=async e=>{
|
|
e.preventDefault();
|
|
messageArea.textContent='';
|
|
const u=normalizeUrl(urlInput.value.trim());
|
|
if(!u)return messageArea.textContent='Please enter a valid URL.';
|
|
const k=grecaptcha.getResponse();
|
|
if(!k)return messageArea.textContent='Please complete the CAPTCHA.';
|
|
submitButton.disabled=true;submitButton.textContent='…';
|
|
try{
|
|
const r=await fetch('/api/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:u,token:k})});
|
|
if(!r.ok)throw new Error(await r.text()||r.statusText);
|
|
const d=await r.json();
|
|
resultContainer.hidden=false;
|
|
resultOutput.value=d.shortUrl;
|
|
openLinkButton.href=d.shortUrl
|
|
}catch(err){
|
|
resultContainer.hidden=true;
|
|
messageArea.textContent=err.message||'Something went wrong.'
|
|
}finally{
|
|
grecaptcha.reset();
|
|
submitButton.disabled=false;
|
|
submitButton.textContent='Shorten'
|
|
}
|
|
};
|
|
|
|
copyButton.onclick=async()=>{
|
|
resultOutput.select();
|
|
try{
|
|
await navigator.clipboard.writeText(resultOutput.value);
|
|
copyButton.innerHTML='<i data-lucide="check" class="w-5 h-5 text-green-500"></i>';
|
|
lucide.createIcons();
|
|
setTimeout(()=>{
|
|
copyButton.innerHTML='<i data-lucide="copy" class="w-5 h-5"></i>';
|
|
lucide.createIcons()
|
|
},1e3)
|
|
}catch(err){}
|
|
};
|
|
|
|
resultOutput.onclick=()=>resultOutput.select();
|
|
urlInput.oninput=()=>{resultContainer.hidden=true;messageArea.textContent=''};
|
|
lucide.createIcons();
|
|
</script>
|
|
</body>
|
|
</html>
|