mirror of
https://github.com/multipleof4/4ev.link.git
synced 2026-01-13 15:57:53 +00:00
Update index.html
This commit is contained in:
182
index.html
Normal file
182
index.html
Normal file
@@ -0,0 +1,182 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-theme="light">
|
||||
<head>
|
||||
<meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<title>4ev.link — Short links that last</title>
|
||||
<meta name="description" content="4ev.link — Free, randomized URL shortener. Clean, fast, and privacy-first."/>
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64'%3E%3Ctext y='50' x='8' font-size='48'%3E🔗%3C/text%3E%3C/svg%3E"/>
|
||||
<style>
|
||||
*{box-sizing:border-box}html,body{height:100%}body{margin:0;font:16px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,'Helvetica Neue',Arial,'Apple Color Emoji','Segoe UI Emoji';background:var(--bg);color:var(--fg)}
|
||||
:root{--bg:#fff;--fg:#0a0a0a;--muted:#666;--line:#e6e6e6;--card:#fff;--soft:#f7f7f7;--shadow:0 1px 2px rgba(0,0,0,.06),0 10px 30px rgba(0,0,0,.08)}
|
||||
[data-theme=dark]{--bg:#0b0b0b;--fg:#f4f4f4;--muted:#a0a0a0;--line:#232323;--card:#111;--soft:#151515;--shadow:0 1px 2px rgba(0,0,0,.4),0 10px 30px rgba(0,0,0,.6)}
|
||||
.container{max-width:1100px;margin:0 auto;padding:20px}
|
||||
.nav{display:flex;align-items:center;justify-content:space-between;padding:10px 0}
|
||||
.brand{display:flex;gap:10px;align-items:center;font-weight:800;font-size:20px;letter-spacing:.2px}
|
||||
.badge{font:12px/1.2 ui-sans-serif;padding:3px 8px;border:1px solid var(--line);border-radius:999px;background:var(--soft);color:var(--muted)}
|
||||
.tools{display:flex;gap:10px;align-items:center}
|
||||
.btn{display:inline-flex;gap:8px;align-items:center;justify-content:center;white-space:nowrap;border:1px solid var(--line);background:var(--card);color:var(--fg);padding:10px 14px;border-radius:10px;box-shadow:var(--shadow);cursor:pointer;transition:.15s ease;user-select:none}
|
||||
.btn:disabled{opacity:.6;cursor:not-allowed;box-shadow:none}
|
||||
.btn:hover{transform:translateY(-1px)}
|
||||
.btn.phantom{background:transparent;box-shadow:none}
|
||||
.hero{display:grid;gap:18px;justify-items:center;text-align:center;padding:40px 0}
|
||||
.h1{font-size:clamp(28px,6vw,44px);letter-spacing:-.02em;line-height:1.08;margin:0}
|
||||
.sub{max-width:680px;color:var(--muted)}
|
||||
.form{display:grid;gap:10px;width:100%;max-width:760px;margin:10px auto 0}
|
||||
.row{display:flex;gap:10px;align-items:center}
|
||||
.input{flex:1;border:1px solid var(--line);background:var(--card);padding:14px 14px;border-radius:12px;outline:none;color:var(--fg);box-shadow:var(--shadow)}
|
||||
.input::placeholder{color:#9a9a9a}
|
||||
.kbd{font:12px/1 ui-monospace,Menlo,Consolas,monospace;border:1px solid var(--line);padding:6px 8px;border-radius:10px;background:var(--soft);color:var(--muted)}
|
||||
.note{font-size:12px;color:var(--muted)}
|
||||
.grid{display:grid;gap:12px;margin:26px auto;grid-template-columns:1fr;max-width:760px}
|
||||
@media(min-width:760px){.grid{grid-template-columns:repeat(3,1fr)}}
|
||||
.card{border:1px solid var(--line);background:var(--card);border-radius:14px;padding:16px;box-shadow:var(--shadow);display:grid;gap:8px}
|
||||
.card h3{margin:0;font-size:15px}
|
||||
.card p{margin:0;color:var(--muted);font-size:13px}
|
||||
.result{display:grid;gap:10px;max-width:760px;margin:18px auto 0}
|
||||
.out{display:flex;gap:10px;align-items:center;justify-content:space-between;border:1px dashed var(--line);background:var(--soft);padding:12px 12px;border-radius:12px}
|
||||
.link{overflow:auto;white-space:nowrap;font:600 14px/1.2 ui-monospace,Menlo,Consolas,monospace}
|
||||
.list{display:grid;gap:8px;margin:20px auto 0;max-width:760px}
|
||||
.item{display:flex;gap:12px;align-items:center;justify-content:space-between;border:1px solid var(--line);background:var(--card);padding:12px;border-radius:12px}
|
||||
.item a{color:inherit;text-decoration:none}
|
||||
.mono{font:12px/1.2 ui-monospace,Menlo,Consolas,monospace;color:var(--muted)}
|
||||
.hr{height:1px;background:var(--line);border:0;margin:26px 0}
|
||||
.footer{display:flex;gap:10px;align-items:center;justify-content:space-between;color:var(--muted);padding:24px 0}
|
||||
.toast{position:fixed;left:50%;bottom:18px;transform:translateX(-50%);background:var(--card);border:1px solid var(--line);box-shadow:var(--shadow);color:var(--fg);padding:10px 14px;border-radius:10px}
|
||||
.skel{animation:sh 1.2s infinite linear;background:linear-gradient(90deg,transparent,rgba(128,128,128,.08),transparent);background-size:200% 100%}
|
||||
@keyframes sh{0%{background-position:200% 0}100%{background-position:-200% 0}}
|
||||
.bgfx:before{content:"";position:fixed;inset:-20% -10% auto -10%;height:50vh;background:radial-gradient(60% 60% at 50% 0,rgba(0,0,0,.06),transparent);pointer-events:none}
|
||||
[data-theme=dark] .bgfx:before{background:radial-gradient(60% 60% at 50% 0,rgba(255,255,255,.04),transparent)}
|
||||
a{color:inherit}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bgfx">
|
||||
<div class="container" x-data="app()" x-init="init()">
|
||||
<header class="nav">
|
||||
<div class="brand">
|
||||
<i data-lucide="link-2" style="width:22px;height:22px"></i>
|
||||
<span>4ev.link</span>
|
||||
<span class="badge">Free • Randomized</span>
|
||||
</div>
|
||||
<div class="tools">
|
||||
<button class="btn phantom" @click="toggleTheme()" :aria-label="t==='dark'?'Switch to light':'Switch to dark'">
|
||||
<i :data-lucide="t==='dark'?'sun':'moon'" style="width:18px;height:18px"></i>
|
||||
</button>
|
||||
<a href="#" class="btn"><i data-lucide="log-in" style="width:18px;height:18px"></i><span>Sign in</span></a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="hero">
|
||||
<h1 class="h1">Short links, built to last</h1>
|
||||
<p class="sub">Clean, fast, privacy-first URL shortener. Free plan with randomized slugs only (e.g., 4ev.link/xY7zQ1). No custom slugs, no ads.</p>
|
||||
<div class="form" @submit.prevent>
|
||||
<div class="row">
|
||||
<input class="input" x-model="url" type="url" inputmode="url" placeholder="Paste a long URL, e.g. https://example.com/very/long/link" aria-label="Long URL" @keydown.enter.prevent="shorten()">
|
||||
<button class="btn" @click="shorten()" :disabled="busy">
|
||||
<template x-if="!busy"><i data-lucide="scissors" style="width:18px;height:18px"></i></template>
|
||||
<template x-if="busy"><i data-lucide="loader-2" style="width:18px;height:18px;animation:spin .9s linear infinite"></i></template>
|
||||
<span x-text="busy?'Working…':'Shorten'"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="row" style="justify-content:space-between">
|
||||
<span class="note">Free tier: randomized links, basic usage. No login required.</span>
|
||||
<span class="kbd">Enter ↵</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="result" x-show="short" x-cloak>
|
||||
<div class="out">
|
||||
<div class="link"><a :href="short" target="_blank" rel="noopener" x-text="short"></a></div>
|
||||
<div class="row" style="gap:8px">
|
||||
<button class="btn" @click="copy(short)"><i data-lucide="copy" style="width:16px;height:16px"></i><span>Copy</span></button>
|
||||
<a class="btn" :href="short" target="_blank" rel="noopener"><i data-lucide="external-link" style="width:16px;height:16px"></i><span>Open</span></a>
|
||||
</div>
|
||||
</div>
|
||||
<small class="note">Tip: Bookmark this page to keep your recent links on this device.</small>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="grid">
|
||||
<article class="card">
|
||||
<div class="row"><i data-lucide="zap" style="width:18px;height:18px"></i><h3>Fast redirects</h3></div>
|
||||
<p>Minimal overhead, instant responses. Built for speed on modern edge platforms.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="row"><i data-lucide="shield" style="width:18px;height:18px"></i><h3>Privacy-first</h3></div>
|
||||
<p>No ad pages. No tracking pixels. Just clean redirects.</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="row"><i data-lucide="wand-2" style="width:18px;height:18px"></i><h3>Randomized slugs</h3></div>
|
||||
<p>Free plan generates secure, random slugs. Custom branded links coming soon.</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<hr class="hr"/>
|
||||
|
||||
<section>
|
||||
<div class="row" style="justify-content:space-between;align-items:center;margin:0 auto 8px;max-width:760px">
|
||||
<h3 style="margin:0;font-size:16px">Recent on this device</h3>
|
||||
<span class="note" x-show="!links.length">No links yet</span>
|
||||
</div>
|
||||
<div class="list">
|
||||
<template x-for="(l,i) in links" :key="l.slug">
|
||||
<div class="item">
|
||||
<div style="min-width:0">
|
||||
<div style="display:flex;gap:8px;align-items:center;min-width:0">
|
||||
<i data-lucide="link" style="width:16px;height:16px"></i>
|
||||
<a :href="`${base}/${l.slug}`" target="_blank" rel="noopener" class="link" x-text="`${base}/${l.slug}`"></a>
|
||||
</div>
|
||||
<div class="mono" style="margin-top:6px;display:flex;gap:8px;align-items:center;flex-wrap:wrap">
|
||||
<span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:60ch" x-text="l.target"></span>
|
||||
<span>•</span>
|
||||
<span><i data-lucide="clock" style="width:14px;height:14px;vertical-align:-2px"></i> <span x-text="new Date(l.created).toLocaleString()"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="gap:8px">
|
||||
<button class="btn" @click="copy(`${base}/${l.slug}`)" title="Copy">
|
||||
<i data-lucide="copy" style="width:16px;height:16px"></i>
|
||||
</button>
|
||||
<a class="btn" :href="`${base}/${l.slug}`" target="_blank" rel="noopener" title="Open">
|
||||
<i data-lucide="external-link" style="width:16px;height:16px"></i>
|
||||
</a>
|
||||
<button class="btn" @click="del(i)" title="Remove">
|
||||
<i data-lucide="trash-2" style="width:16px;height:16px"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="footer">
|
||||
<div style="display:flex;gap:10px;align-items:center">
|
||||
<i data-lucide="globe" style="width:16px;height:16px"></i>
|
||||
<span>© <span x-text="new Date().getFullYear()"></span> 4ev.link</span>
|
||||
</div>
|
||||
<div class="note">Free plan • Randomized links • Black/White/Gray UI</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<script>
|
||||
const app=()=>({t:localStorage.getItem('theme')||(matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'),url:'',busy:0,short:'',links:JSON.parse(localStorage.getItem('4ev.links')||'[]'),base:'https://4ev.link',slugLen:6,msg:'',
|
||||
init(){document.documentElement.setAttribute('data-theme',this.t);this.freshIcons();},
|
||||
setTheme(v){this.t=v;localStorage.setItem('theme',v);document.documentElement.setAttribute('data-theme',v);this.freshIcons()},
|
||||
toggleTheme(){this.setTheme(this.t==='light'?'dark':'light')},
|
||||
val(u){try{let x=new URL(u);return['http:','https:'].includes(x.protocol)}catch{return!1}},
|
||||
rand(n){let c='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',s='';for(let i=0;i<n;i++)s+=c[(Math.random()*c.length)|0];return s},
|
||||
uniqueSlug(){let s;do s=this.rand(this.slugLen);while(this.links.find(l=>l.slug===s));return s},
|
||||
shorten(){if(this.busy)return;if(!this.val(this.url))return this.toast('Enter a valid URL');this.busy=1;setTimeout(()=>{let slug=this.uniqueSlug(),t=this.url.trim();this.short=`${this.base}/${slug}`;this.links.unshift({slug:slug,target:t,created:Date.now()});this.links=this.links.slice(0,20);localStorage.setItem('4ev.links',JSON.stringify(this.links));this.url='';this.busy=0;this.toast('Link created');this.freshIcons()},450)},
|
||||
copy(txt){navigator.clipboard?.writeText(txt).then(()=>this.toast('Copied')).catch(()=>{let ta=document.createElement('textarea');ta.value=txt;document.body.appendChild(ta);ta.select();document.execCommand('copy');ta.remove();this.toast('Copied')})},
|
||||
toast(m){this.msg=m;this.freshIcons();clearTimeout(this._to);this._to=setTimeout(()=>this.msg='',2000)},
|
||||
del(i){this.links.splice(i,1);localStorage.setItem('4ev.links',JSON.stringify(this.links));this.freshIcons()},
|
||||
freshIcons(){try{lucide&&lucide.createIcons()}catch(e){}}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded',()=>{try{lucide.createIcons()}catch(e){}});
|
||||
</script>
|
||||
<style>@keyframes spin{to{transform:rotate(360deg)}}</style>
|
||||
<div x-data="{m:''}" x-init="$watch('$root.__x.$data.msg',v=>{m=v})">
|
||||
<div class="toast" x-cloak x-show="$root.__x.$data.msg" x-text="$root.__x.$data.msg" role="status" aria-live="polite"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user