Files
4ev.link/index.html

111 lines
6.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html><html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>4ev.link — short links that last</title>
<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@400;600&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
<style>
:root{--bg:#fff;--fg:#0a0a0a;--mut:#6b7280;--line:#e5e7eb;--card:#fafafa;--ring:#11182722;--red:#ef4444;--ok:#10b981;--shadow:0 10px 30px rgba(0,0,0,.06)}
*{box-sizing:border-box}html,body{height:100%;background:var(--bg);color:var(--fg)}
body{margin:0;font:16px/1.4 Inter,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,"Helvetica Neue",Arial,"Apple Color Emoji","Segoe UI Emoji";-webkit-font-smoothing:antialiased}
a{color:inherit;text-decoration:none}
.btn{display:inline-flex;align-items:center;gap:.5rem;border:1px solid var(--line);background:#fff;color:#111;padding:.7rem 1rem;border-radius:10px;cursor:pointer;box-shadow:0 1px 0 #00000008;transition:.15s}
.btn:hover{transform:translateY(-1px);box-shadow:0 6px 16px #00000014}
.btn.pri{background:#111;color:#fff;border-color:#111}
.btn.ghost{background:transparent}
.btn:disabled{opacity:.6;cursor:not-allowed;transform:none;box-shadow:none}
.input{width:100%;padding:.9rem 1rem;border:1px solid var(--line);border-radius:12px;background:#fff;outline:none;transition:border .15s,box-shadow .15s}
.input:focus{border-color:#111;box-shadow:0 0 0 4px var(--ring)}
.small{font-size:.84rem;color:var(--mut)}
.container{max-width:860px;margin:0 auto;padding:16px}
.header{position:relative;display:grid;place-items:center;height:64px}
.header .login{position:absolute;right:8px;top:8px}
.brand{display:flex;align-items:center;gap:.5rem;font-weight:700}
.heart{color:var(--red)}
.main{min-height:calc(100svh - 64px);display:grid;place-items:center}
.hero{width:100%;max-width:680px;display:grid;gap:16px}
.card{background:var(--card);border:1px solid var(--line);border-radius:16px;box-shadow:var(--shadow);padding:14px}
.row{display:flex;gap:8px}
.row.col{flex-direction:column}
.split{display:grid;grid-template-columns:1fr auto;gap:8px}
.kbd{font-family:"Space Mono",ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}
.badge{display:inline-flex;align-items:center;gap:.35rem;border:1px solid var(--line);border-radius:999px;padding:.35rem .6rem;background:#fff}
.plans{display:flex;gap:8px;flex-wrap:wrap}
.hr{height:1px;background:var(--line);margin:8px 0}
.center{text-align:center}
footer{color:var(--mut);font-size:.8rem}
.hide{display:none}
@media(min-width:720px){.hero{gap:18px}.header{height:72px}.header .login{top:14px;right:14px}}
</style>
</head>
<body>
<div class="container">
<header class="header">
<a href="/login" class="login btn ghost" aria-label="Log in"><i data-lucide="log-in"></i><span>Log in</span></a>
<h1 class="brand center">Get ur 4ev.link <i class="heart" data-lucide="heart"></i></h1>
</header>
<main class="main">
<section class="hero" x-data="{long:'',short:'',err:'',busy:0,copied:0,ex:'https://4ev.link/a1B2'}" @keyup.window.escape="long='';short='';err=''">
<p class="center small">Free works—no account needed. Cloudflare-inspired. Black/white/gray. Light. Mobile-first.</p>
<div class="card">
<form class="row col" @submit.prevent="
err='';short='';busy=1;
fetch('/',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({url:long.trim()})})
.then(r=>r.ok?r.json():Promise.reject(r))
.then(d=>short=d.shortUrl)
.catch(()=>err='Please enter a valid URL (incl. https://)')
.finally(()=>busy=0)
">
<label class="small" for="long">Paste your long URL</label>
<div class="split">
<input id="long" class="input kbd" x-model.trim="long" type="url" inputmode="url" spellcheck="false" placeholder="https://example.com/very/long/path?with=params" required>
<button class="btn pri" :disabled="busy||!long">
<template x-if="!busy"><i data-lucide="link-2"></i></template>
<template x-if="busy"><i data-lucide="loader-2" class="spin"></i></template>
<span x-text="busy?'Shortening…':'Shorten'"></span>
</button>
</div>
<p class="small" :class="err?'':'hide'" style="color:#dc2626" x-text="err"></p>
<template x-if="short">
<div class="row col" x-init="$nextTick(()=>lucide.createIcons())">
<label class="small" for="short">Your short link</label>
<div class="split">
<input id="short" class="input kbd" :value="short" readonly @focus="$el.select()">
<button type="button" class="btn" @click="navigator.clipboard.writeText(short).then(()=>{copied=1;setTimeout(()=>copied=0,1500)})">
<i data-lucide="copy"></i><span x-text="copied?'Copied!':'Copy'"></span>
</button>
</div>
<p class="small">Tip: its a 302 redirect. Keep your original live.</p>
</div>
</template>
</form>
</div>
<div class="row col">
<div class="plans">
<span class="badge"><i data-lucide="sparkles"></i><b>Free</b>: random 4-char slugs <span class="kbd small" style="color:#111">/a1B2</span></span>
<span class="badge"><i data-lucide="wand-2"></i><b>Paid</b>: custom aliases <span class="small" style="color:var(--mut)">(coming soon)</span></span>
</div>
<div class="hr"></div>
<p class="small"><i data-lucide="info"></i> Example: <span class="kbd">https://your.site/blog</span><span class="kbd" x-text="ex"></span></p>
</div>
<footer class="center">© <span x-text="new Date().getFullYear()"></span> 4ev.link · Built on the edge · <a class="small" href="https://lucide.dev" target="_blank" rel="noreferrer">Lucide</a> · <a class="small" href="https://alpinejs.dev" target="_blank" rel="noreferrer">Alpine</a></footer>
</section>
</main>
</div>
<script src="//unpkg.com/alpinejs" defer></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script>
lucide.createIcons();
document.querySelectorAll('.spin')?.forEach(el=>el.style.cssText+='animation:spin 1s linear infinite');
(function(){const s=document.createElement('style');s.textContent='@keyframes spin{to{transform:rotate(360deg)}}';document.head.appendChild(s)})();
</script>
</body>
</html>