Feat: Minimal isomorphic landing page

This commit is contained in:
2025-09-26 20:27:33 -07:00
parent ad5105d445
commit bc25e7be69

View File

@@ -1,61 +1,24 @@
<!doctype html><html lang=en> <!doctype html><html lang=en><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>4ev.link — shorten URLs</title><meta name=color-scheme content="light dark"><meta name=theme-color content=#ffffff media="(prefers-color-scheme: light)"><meta name=theme-color content=#000000 media="(prefers-color-scheme: dark)"><style>
<head> *{box-sizing:border-box}body{margin:0;display:grid;place-items:center;min-height:100dvh;background:#fff;color:#111;font:16px/1.4 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,"Helvetica Neue",Arial}::selection{background:#0001}@media(prefers-color-scheme:dark){body{background:#000;color:#ddd}input,button{background:#111;color:#eee;border-color:#333}.muted{color:#999}}.w{width:min(680px,92vw)}h1{margin:0 0 12px;font:600 28px/1.1 system-ui,-apple-system,Segoe UI,Roboto}form{display:flex;gap:8px}input[type=text],#o{flex:1;padding:12px 14px;border:1px solid #ddd;border-radius:12px;outline:0;transition:.15s}input:focus{border-color:#000}button{padding:12px 16px;border:1px solid #000;background:#000;color:#fff;border-radius:12px;cursor:pointer;transition:.15s}button:disabled{opacity:.6;cursor:wait}#res{display:flex;gap:8px;margin-top:12px;align-items:center}.muted{font-size:12px;color:#666;margin-top:8px}#m{color:#c00;margin-top:8px;font-size:14px}
<meta charset=utf-8> </style><main class=w>
<meta name=viewport content="width=device-width,initial-scale=1,viewport-fit=cover"> <h1><a href=/ style="text-decoration:none;color:inherit">4ev.link</a></h1>
<title>4ev.link — Shorten URLs</title> <form id=f autocomplete=on>
<meta name=color-scheme content=light> <input id=i type=text placeholder="Paste a link or type: google.com" autocapitalize=off spellcheck=false autofocus>
<link rel="icon" href="data:,"> <button id=b type=submit>Shorten</button>
<style> </form>
*{box-sizing:border-box}html,body{height:100%}body{margin:0;font:16px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif;background:#fff;color:#111;-webkit-font-smoothing:antialiased} <div id=res hidden>
a{color:inherit} <input id=o type=text readonly title="Your short link">
header{padding:16px} <button id=c type=button>Copy</button>
.brand{font-weight:700;text-decoration:none} <a id=a target=_blank rel=noopener>Open</a>
main{max-width:720px;margin:14vh auto 0;padding:0 16px} </div>
form{display:flex;gap:8px} <p id=m></p>
input{flex:1;padding:14px 12px;border:1px solid #e5e7eb;border-radius:0;outline-offset:2px} <p class=muted>Shorten URLs that last. Works with domains or full links.</p>
button{padding:0 14px;border:1px solid #e5e7eb;border-radius:0;background:#111;color:#fff;display:inline-grid;place-items:center;min-width:48px} <noscript class=muted>Enable JavaScript to shorten links.</noscript>
button:disabled{opacity:.7;cursor:not-allowed}
i[data-lucide]{width:22px;height:22px}
#o{margin-top:12px}
.msg{margin-top:8px;color:#666;display:flex;gap:8px;align-items:center}
.err{color:#b00020}
.result{display:flex;align-items:center;gap:8px;margin-top:12px;flex-wrap:wrap}
.tag{padding:8px 10px;border:1px solid #e5e7eb;background:#fafafa}
.copy{border:1px solid #e5e7eb;background:#fff;color:#111;padding:8px 10px}
.spin{animation:sp 1s linear infinite}@keyframes sp{to{transform:rotate(1turn)}}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
</style>
</head>
<body>
<header><a class=brand href="/">4ev.link</a></header>
<main>
<form id=f autocomplete=off>
<input id=i type=url inputmode=url placeholder="Paste a long URL" aria-label="Long URL" required>
<button id=b type=submit title=Shorten aria-label=Shorten><i data-lucide=link-2></i><span class=sr-only>Shorten</span></button>
</form>
<output id=o role=status aria-live=polite></output>
</main> </main>
<script src="https://unpkg.com/lucide@latest"></script>
<script> <script>
(()=>{const $=q=>document.querySelector(q),f=$('#f'),I=$('#i'),B=$('#b'),O=$('#o'),ico=()=>{try{lucide.createIcons()}catch{}},set=(h)=>{O.innerHTML=h,ico()} const $=q=>document.querySelector(q),f=$('#f'),i=$('#i'),b=$('#b'),m=$('#m'),R=$('#res'),o=$('#o'),c=$('#c'),a=$('#a'),norm=u=>{try{return new URL(u).href}catch(_){try{return new URL('https://'+u).href}catch(_){}}};
ico(); f.onsubmit=async e=>{e.preventDefault();m.textContent='';let u=norm(i.value.trim());if(!u)return m.textContent='Enter a valid URL';b.disabled=1;b.textContent='…';try{let r=await fetch('/api/create',{method:'POST',headers:{'Content-Type':'application/json','Accept':'application/json'},body:JSON.stringify({url:u})});if(!r.ok)throw new Error(await r.text()||r.statusText);let d=await r.json();R.hidden=0;o.value=d.shortUrl;a.href=d.shortUrl;o.focus();o.select()}catch(err){R.hidden=1;m.textContent=err.message||'Something went wrong'}b.disabled=0;b.textContent='Shorten'};
f.onsubmit=async e=>{ c.onclick=async()=>{o.select();try{await navigator.clipboard.writeText(o.value);c.textContent='Copied!';setTimeout(_=>c.textContent='Copy',800)}catch(_){document.execCommand('copy')}};
e.preventDefault(); o.onclick=_=>o.select();i.oninput=_=>{R.hidden=1;m.textContent=''};
let v=I.value.trim(); if(!v)return;
if(!/^https?:\/\//i.test(v))v='https://'+v;
B.disabled=1;const i=B.querySelector('i');i.setAttribute('data-lucide','loader-2');i.classList.add('spin');ico();
try{
const r=await fetch('/api/create',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:v})});
if(!r.ok)throw 0;const j=await r.json(),u=j.shortUrl;
set(`<div class=result><a class=tag href="${u}" target=_blank rel=noopener>${u}</a><button id=c class=copy type=button title=Copy><i data-lucide=copy></i></button></div><p class=msg>Tap to open, or copy to share.</p>`);
$('#c').onclick=async()=>{try{await navigator.clipboard.writeText(u);const k=$('#c i');k.setAttribute('data-lucide','check');ico();setTimeout(()=>{k.setAttribute('data-lucide','copy');ico()},1200)}catch{}}
}catch{
set(`<p class="msg err"><i data-lucide=alert-circle></i>Invalid URL</p>`)
}finally{
B.disabled=0;i.classList.remove('spin');i.setAttribute('data-lucide','link-2');ico()
}
};
})();
</script> </script>
</body>
</html> </html>