mirror of
https://github.com/multipleof4/4ev.link.git
synced 2026-01-13 23:57:55 +00:00
111 lines
6.6 KiB
HTML
111 lines
6.6 KiB
HTML
<!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: it’s 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>
|