mirror of
https://github.com/4ev-link/4ev.link.git
synced 2026-01-13 16:18:05 +00:00
Feat: Apply custom fonts for marketing/auth
This commit is contained in:
191
index.html
191
index.html
@@ -9,65 +9,161 @@
|
||||
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<script src="https://unpkg.com/lucide@latest"></script>
|
||||
<script src="https://www.google.com/recaptcha/api.js?render=explicit" async defer></script>
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=plus-jakarta-sans:400,500,600,700|general-sans:600,700|red-hat-mono:500" rel="stylesheet" />
|
||||
<style>
|
||||
:root{
|
||||
--font-ui:"Plus Jakarta Sans",-apple-system,BlinkMacSystemFont,system-ui,sans-serif;
|
||||
--font-display:"General Sans","Plus Jakarta Sans",-apple-system,BlinkMacSystemFont,system-ui,sans-serif;
|
||||
--font-mono:"Red Hat Mono",ui-monospace,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace
|
||||
}
|
||||
body{font-family:var(--font-ui)}
|
||||
.font-display{font-family:var(--font-display)}
|
||||
.font-mono-custom{font-family:var(--font-mono)}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-slate-900 text-slate-200 min-h-screen flex items-center justify-center font-sans overflow-hidden">
|
||||
<body class="bg-slate-900 text-slate-200 min-h-screen flex items-center justify-center overflow-hidden">
|
||||
<script>if(localStorage.getItem("username"))window.location.href="/dash/";</script>
|
||||
|
||||
<div class="absolute top-0 left-0 w-72 h-72 bg-gradient-to-tr from-violet-600 to-rose-600 rounded-full filter blur-3xl opacity-30 animate-blob"></div>
|
||||
<div class="absolute bottom-0 right-0 w-72 h-72 bg-gradient-to-br from-blue-600 to-green-600 rounded-full filter blur-3xl opacity-30 animate-blob animation-delay-4000"></div>
|
||||
|
||||
<main x-data="{ view:'landing' }" class="relative z-10 w-full max-w-md mx-auto p-4 text-center">
|
||||
<div x-show="view==='landing'" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 transform scale-90" x-transition:enter-end="opacity-100 transform scale-100" x-transition:leave="transition ease-in duration-200" x-transition:leave-start="opacity-100 transform scale-100" x-transition:leave-end="opacity-0 transform scale-90">
|
||||
<h1 class="text-5xl font-bold mb-2 flex items-center justify-center gap-3">
|
||||
<i data-lucide="link" class="w-10 h-10"></i> 4ev.link
|
||||
<div
|
||||
x-show="view==='landing'"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 transform scale-90"
|
||||
x-transition:enter-end="opacity-100 transform scale-100"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 transform scale-100"
|
||||
x-transition:leave-end="opacity-0 transform scale-90"
|
||||
>
|
||||
<h1 class="text-5xl font-bold mb-2 flex items-center justify-center gap-3 font-display tracking-tight">
|
||||
<i data-lucide="link" class="w-10 h-10"></i>
|
||||
<span class="leading-none">4ev.link</span>
|
||||
</h1>
|
||||
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/40 text-emerald-300 text-xs font-semibold mb-3">
|
||||
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/40 text-emerald-300 text-[10px] font-semibold mb-3 uppercase tracking-[0.16em]">
|
||||
<span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span>
|
||||
Now with analytics!
|
||||
Now with analytics
|
||||
</div>
|
||||
<p class="text-slate-400 mb-8 max-w-sm mx-auto">
|
||||
If you want short custom urls, like url shortening to a custom slug, our platform is for you.
|
||||
<p class="text-slate-400 mb-8 max-w-sm mx-auto text-sm">
|
||||
Permanent, custom short URLs built on the edge. Fast, reliable, and yours
|
||||
<span class="font-mono-custom text-slate-300">4ev.link/you</span>.
|
||||
</p>
|
||||
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||
<button @click="view='login'" class="w-full sm:w-auto px-6 py-3 font-semibold rounded-lg bg-slate-800 hover:bg-slate-700 transition-colors duration-200">
|
||||
<button
|
||||
@click="view='login'"
|
||||
class="w-full sm:w-auto px-6 py-3 font-semibold rounded-lg bg-slate-900/80 hover:bg-slate-800 transition-colors duration-200 border border-slate-700 text-sm tracking-tight"
|
||||
>
|
||||
Login
|
||||
</button>
|
||||
<button @click="view='signup'" class="w-full sm:w-auto px-6 py-3 font-semibold rounded-lg text-white bg-gradient-to-r from-sky-500 to-indigo-500 hover:opacity-90 transition-opacity duration-200 flex items-center justify-center gap-2">
|
||||
Sign Up Now <i data-lucide="arrow-right" class="w-4 h-4"></i>
|
||||
<button
|
||||
@click="view='signup'"
|
||||
class="w-full sm:w-auto px-6 py-3 font-semibold rounded-lg text-slate-50 bg-gradient-to-r from-sky-500 to-indigo-500 hover:opacity-90 transition-opacity duration-200 flex items-center justify-center gap-2 text-sm tracking-tight"
|
||||
>
|
||||
Sign Up Now
|
||||
<i data-lucide="arrow-right" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div x-data="authForm()" x-effect="view;renderCaptcha()" x-show="view!=='landing'" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="opacity-0 transform scale-90" x-transition:enter-end="opacity-100 transform scale-100" x-transition:leave="transition ease-in duration-200" x-transition:leave-start="opacity-100 transform scale-100" x-transition:leave-end="opacity-0 transform scale-90" class="bg-slate-800/50 backdrop-blur-sm p-8 rounded-xl border border-slate-700" style="display:none;">
|
||||
<button @click="view='landing';error='';" class="absolute top-4 left-4 text-slate-400 hover:text-white transition-colors">
|
||||
<div
|
||||
x-data="authForm()"
|
||||
x-effect="view;renderCaptcha()"
|
||||
x-show="view!=='landing'"
|
||||
x-transition:enter="transition ease-out duration-300"
|
||||
x-transition:enter-start="opacity-0 transform scale-90"
|
||||
x-transition:enter-end="opacity-100 transform scale-100"
|
||||
x-transition:leave="transition ease-in duration-200"
|
||||
x-transition:leave-start="opacity-100 transform scale-100"
|
||||
x-transition:leave-end="opacity-0 transform scale-90"
|
||||
class="bg-slate-900/70 backdrop-blur-sm p-8 rounded-xl border border-slate-700 text-left"
|
||||
style="display:none;"
|
||||
>
|
||||
<button
|
||||
@click="view='landing';error='';"
|
||||
class="absolute top-4 left-4 text-slate-400 hover:text-white transition-colors"
|
||||
>
|
||||
<i data-lucide="arrow-left"></i>
|
||||
</button>
|
||||
<template x-if="view==='login'">
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold mb-6">Login</h2>
|
||||
<h2 class="text-3xl font-semibold mb-6 font-display tracking-tight">Login</h2>
|
||||
<form @submit.prevent="submit('signin')" class="space-y-4">
|
||||
<input autocomplete="username" type="text" x-model="username" placeholder="Username" required class="w-full p-3 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-sky-500">
|
||||
<input autocomplete="current-password" type="password" x-model="password" placeholder="Password" required class="w-full p-3 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-sky-500">
|
||||
<input
|
||||
autocomplete="username"
|
||||
type="text"
|
||||
x-model="username"
|
||||
placeholder="Username"
|
||||
required
|
||||
class="w-full p-3 bg-slate-900 border border-slate-700 rounded-md focus:outline-none focus:ring-2 focus:ring-sky-500 text-sm"
|
||||
>
|
||||
<input
|
||||
autocomplete="current-password"
|
||||
type="password"
|
||||
x-model="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
class="w-full p-3 bg-slate-900 border border-slate-700 rounded-md focus:outline-none focus:ring-2 focus:ring-sky-500 text-sm"
|
||||
>
|
||||
<div class="recaptcha-container flex justify-center"></div>
|
||||
<p x-text="error" x-show="error" class="text-rose-400 text-sm h-5 !-mt-2 text-center"></p>
|
||||
<button type="submit" :disabled="loading" class="w-full py-3 font-semibold rounded-lg text-white bg-gradient-to-r from-sky-500 to-indigo-500 hover:opacity-90 transition-opacity flex items-center justify-center disabled:opacity-50">
|
||||
<p
|
||||
x-text="error"
|
||||
x-show="error"
|
||||
class="text-rose-400 text-xs h-5 !-mt-2 text-center"
|
||||
></p>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="loading"
|
||||
class="w-full py-3 font-semibold rounded-lg text-slate-50 bg-gradient-to-r from-sky-500 to-indigo-500 hover:opacity-90 transition-opacity flex items-center justify-center disabled:opacity-50 text-sm"
|
||||
>
|
||||
<span x-show="!loading">Login</span>
|
||||
<i x-show="loading" data-lucide="loader-2" class="animate-spin w-6 h-6"></i>
|
||||
<i
|
||||
x-show="loading"
|
||||
data-lucide="loader-2"
|
||||
class="animate-spin w-6 h-6"
|
||||
></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="view==='signup'">
|
||||
<div>
|
||||
<h2 class="text-3xl font-bold mb-6">Create Account</h2>
|
||||
<h2 class="text-3xl font-semibold mb-6 font-display tracking-tight">Create Account</h2>
|
||||
<form @submit.prevent="submit('signup')" class="space-y-4">
|
||||
<input autocomplete="off" type="text" x-model="username" placeholder="Username" required class="w-full p-3 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<input autocomplete="new-password" type="password" x-model="password" placeholder="Password" required class="w-full p-3 bg-slate-700 border border-slate-600 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
|
||||
<div class="recaptcha-container flex justify-center"></div>
|
||||
<p x-text="error" x-show="error" class="text-rose-400 text-sm h-5 !-mt-2 text-center"></p>
|
||||
<button type="submit" :disabled="loading" class="w-full py-3 font-semibold rounded-lg text-white bg-gradient-to-r from-sky-500 to-indigo-500 hover:opacity-90 transition-opacity flex items-center justify-center disabled:opacity-50">
|
||||
<input
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
x-model="username"
|
||||
placeholder="Username"
|
||||
required
|
||||
class="w-full p-3 bg-slate-900 border border-slate-700 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 text-sm"
|
||||
>
|
||||
<input
|
||||
autocomplete="new-password"
|
||||
type="password"
|
||||
x-model="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
class="w-full p-3 bg-slate-900 border border-slate-700 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500 text-sm"
|
||||
>
|
||||
<div class="recaptcha-container flex justify-center"></div>
|
||||
<p
|
||||
x-text="error"
|
||||
x-show="error"
|
||||
class="text-rose-400 text-xs h-5 !-mt-2 text-center"
|
||||
></p>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="loading"
|
||||
class="w-full py-3 font-semibold rounded-lg text-slate-50 bg-gradient-to-r from-sky-500 to-indigo-500 hover:opacity-90 transition-opacity flex items-center justify-center disabled:opacity-50 text-sm"
|
||||
>
|
||||
<span x-show="!loading">Sign Up</span>
|
||||
<i x-show="loading" data-lucide="loader-2" class="animate-spin w-6 h-6"></i>
|
||||
<i
|
||||
x-show="loading"
|
||||
data-lucide="loader-2"
|
||||
class="animate-spin w-6 h-6"
|
||||
></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
@@ -75,9 +171,11 @@
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="absolute bottom-4 left-0 right-0 text-center text-slate-500 text-sm z-10">
|
||||
<a href="https://blog.4ev.link" target="_blank" class="hover:text-slate-300 transition-colors">Blog</a> •
|
||||
<a href="/acceptable-use" class="hover:text-slate-300 transition-colors">Acceptable Use</a> •
|
||||
<footer class="absolute bottom-4 left-0 right-0 text-center text-slate-500 text-xs z-10 font-ui">
|
||||
<a href="https://blog.4ev.link" target="_blank" class="hover:text-slate-300 transition-colors">Blog</a>
|
||||
•
|
||||
<a href="/acceptable-use" class="hover:text-slate-300 transition-colors">Acceptable Use</a>
|
||||
•
|
||||
<a href="/abuse" class="hover:text-slate-300 transition-colors">Abuse</a>
|
||||
</footer>
|
||||
|
||||
@@ -101,29 +199,44 @@
|
||||
loading:!1,
|
||||
widgetId:null,
|
||||
renderCaptcha(){
|
||||
if(!window.grecaptcha?.render)return void setTimeout(()=>this.renderCaptcha(),100);
|
||||
if(!window.grecaptcha?.render)
|
||||
return void setTimeout(()=>this.renderCaptcha(),100);
|
||||
this.$nextTick(()=>{
|
||||
const e=this.$el.querySelector(".recaptcha-container");
|
||||
e&&(e.innerHTML="",this.widgetId=grecaptcha.render(e,{sitekey:"6LeXhdYrAAAAALW6DdgxNeHU0kwBncdicLnVYvXT"}))
|
||||
e&&(e.innerHTML="",
|
||||
this.widgetId=grecaptcha.render(e,{
|
||||
sitekey:"6LeXhdYrAAAAALW6DdgxNeHU0kwBncdicLnVYvXT"
|
||||
}))
|
||||
})
|
||||
},
|
||||
async submit(e){
|
||||
this.loading=!0,this.error="";
|
||||
this.loading=!0,
|
||||
this.error="";
|
||||
const c=grecaptcha.getResponse(this.widgetId);
|
||||
if(!c)return this.error="Please complete the CAPTCHA.",this.loading=!1,void 0;
|
||||
if(!c)
|
||||
return this.error="Please complete the CAPTCHA.",
|
||||
this.loading=!1,
|
||||
void 0;
|
||||
try{
|
||||
const t=new TextEncoder,s=t.encode(this.password),o=t.encode(this.username.padEnd(16,"0").substring(0,16)),
|
||||
const t=new TextEncoder,
|
||||
s=t.encode(this.password),
|
||||
o=t.encode(this.username.padEnd(16,"0").substring(0,16)),
|
||||
a=await scrypt.scrypt(s,o,16384,8,1,32),
|
||||
n=Array.from(a).map(e=>e.toString(16).padStart(2,"0")).join(""),
|
||||
n=Array.from(a).map(r=>r.toString(16).padStart(2,"0")).join(""),
|
||||
i=await fetch(`/api/${e}`,{
|
||||
method:"POST",
|
||||
headers:{"Content-Type":"application/json"},
|
||||
body:JSON.stringify({username:this.username,pass_hash:n,"g-recaptcha-response":c})
|
||||
body:JSON.stringify({
|
||||
username:this.username,
|
||||
pass_hash:n,
|
||||
"g-recaptcha-response":c
|
||||
})
|
||||
});
|
||||
if(!i.ok)throw new Error(await i.text()||"An error occurred");
|
||||
const r=await i.json();
|
||||
if(!i.ok)
|
||||
throw new Error(await i.text()||"An error occurred");
|
||||
const l=await i.json();
|
||||
localStorage.setItem("pass_hash",n),
|
||||
localStorage.setItem("username",r.username),
|
||||
localStorage.setItem("username",l.username),
|
||||
window.location.href="/dash/"
|
||||
}catch(t){
|
||||
this.error=t.message
|
||||
|
||||
Reference in New Issue
Block a user